xref: /freebsd/contrib/atf/atf-c/detail/tp_main.c (revision 8f0ea33f2bbf3a6aa80235f0a02fa5f2780c2b17)
1  /* Copyright (c) 2008 The NetBSD Foundation, Inc.
2   * All rights reserved.
3   *
4   * Redistribution and use in source and binary forms, with or without
5   * modification, are permitted provided that the following conditions
6   * are met:
7   * 1. Redistributions of source code must retain the above copyright
8   *    notice, this list of conditions and the following disclaimer.
9   * 2. Redistributions in binary form must reproduce the above copyright
10   *    notice, this list of conditions and the following disclaimer in the
11   *    documentation and/or other materials provided with the distribution.
12   *
13   * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
14   * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
15   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17   * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
18   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
20   * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
22   * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23   * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
24   * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */
25  
26  #if defined(HAVE_CONFIG_H)
27  #include "config.h"
28  #endif
29  
30  #include <ctype.h>
31  #include <stdarg.h>
32  #include <stdio.h>
33  #include <stdlib.h>
34  #include <string.h>
35  #include <unistd.h>
36  
37  #include "atf-c/detail/dynstr.h"
38  #include "atf-c/detail/env.h"
39  #include "atf-c/detail/fs.h"
40  #include "atf-c/detail/map.h"
41  #include "atf-c/detail/sanity.h"
42  #include "atf-c/error.h"
43  #include "atf-c/tc.h"
44  #include "atf-c/tp.h"
45  #include "atf-c/utils.h"
46  
47  #if defined(HAVE_GNU_GETOPT)
48  #   define GETOPT_POSIX "+"
49  #else
50  #   define GETOPT_POSIX ""
51  #endif
52  
53  static const char *progname = NULL;
54  
55  /* This prototype is provided by macros.h during instantiation of the test
56   * program, so it can be kept private.  Don't know if that's the best idea
57   * though. */
58  int atf_tp_main(int, char **, atf_error_t (*)(atf_tp_t *));
59  
60  enum tc_part {
61      BODY,
62      CLEANUP,
63  };
64  
65  /* ---------------------------------------------------------------------
66   * The "usage" and "user" error types.
67   * --------------------------------------------------------------------- */
68  
69  #define FREE_FORM_ERROR(name) \
70      struct name ## _error_data { \
71          char m_what[2048]; \
72      }; \
73      \
74      static \
75      void \
76      name ## _format(const atf_error_t err, char *buf, size_t buflen) \
77      { \
78          const struct name ## _error_data *data; \
79          \
80          PRE(atf_error_is(err, #name)); \
81          \
82          data = atf_error_data(err); \
83          snprintf(buf, buflen, "%s", data->m_what); \
84      } \
85      \
86      static \
87      atf_error_t \
88      name ## _error(const char *fmt, ...) \
89      { \
90          atf_error_t err; \
91          struct name ## _error_data data; \
92          va_list ap; \
93          \
94          va_start(ap, fmt); \
95          vsnprintf(data.m_what, sizeof(data.m_what), fmt, ap); \
96          va_end(ap); \
97          \
98          err = atf_error_new(#name, &data, sizeof(data), name ## _format); \
99          \
100          return err; \
101      }
102  
103  FREE_FORM_ERROR(usage);
104  FREE_FORM_ERROR(user);
105  
106  /* ---------------------------------------------------------------------
107   * Printing functions.
108   * --------------------------------------------------------------------- */
109  
110  static
111  void
print_error(const atf_error_t err)112  print_error(const atf_error_t err)
113  {
114      char buf[4096];
115  
116      PRE(atf_is_error(err));
117  
118      atf_error_format(err, buf, sizeof(buf));
119      fprintf(stderr, "%s: ERROR: %s\n", progname, buf);
120  
121      if (atf_error_is(err, "usage"))
122          fprintf(stderr, "%s: See atf-test-program(1) for usage details.\n",
123                  progname);
124  }
125  
126  static
127  void
print_warning(const char * message)128  print_warning(const char *message)
129  {
130      fprintf(stderr, "%s: WARNING: %s\n", progname, message);
131  }
132  
133  /* ---------------------------------------------------------------------
134   * Options handling.
135   * --------------------------------------------------------------------- */
136  
137  struct params {
138      bool m_do_list;
139      atf_fs_path_t m_srcdir;
140      char *m_tcname;
141      enum tc_part m_tcpart;
142      atf_fs_path_t m_resfile;
143      atf_map_t m_config;
144  };
145  
146  static
147  atf_error_t
argv0_to_dir(const char * argv0,atf_fs_path_t * dir)148  argv0_to_dir(const char *argv0, atf_fs_path_t *dir)
149  {
150      atf_error_t err;
151      atf_fs_path_t temp;
152  
153      err = atf_fs_path_init_fmt(&temp, "%s", argv0);
154      if (atf_is_error(err))
155          goto out;
156  
157      err = atf_fs_path_branch_path(&temp, dir);
158  
159      atf_fs_path_fini(&temp);
160  out:
161      return err;
162  }
163  
164  static
165  atf_error_t
params_init(struct params * p,const char * argv0)166  params_init(struct params *p, const char *argv0)
167  {
168      atf_error_t err;
169  
170      p->m_do_list = false;
171      p->m_tcname = NULL;
172      p->m_tcpart = BODY;
173  
174      err = argv0_to_dir(argv0, &p->m_srcdir);
175      if (atf_is_error(err))
176          return err;
177  
178      err = atf_fs_path_init_fmt(&p->m_resfile, "/dev/stdout");
179      if (atf_is_error(err)) {
180          atf_fs_path_fini(&p->m_srcdir);
181          return err;
182      }
183  
184      err = atf_map_init(&p->m_config);
185      if (atf_is_error(err)) {
186          atf_fs_path_fini(&p->m_resfile);
187          atf_fs_path_fini(&p->m_srcdir);
188          return err;
189      }
190  
191      return err;
192  }
193  
194  static
195  void
params_fini(struct params * p)196  params_fini(struct params *p)
197  {
198      atf_map_fini(&p->m_config);
199      atf_fs_path_fini(&p->m_resfile);
200      atf_fs_path_fini(&p->m_srcdir);
201      if (p->m_tcname != NULL)
202          free(p->m_tcname);
203  }
204  
205  static
206  atf_error_t
parse_vflag(char * arg,atf_map_t * config)207  parse_vflag(char *arg, atf_map_t *config)
208  {
209      atf_error_t err;
210      char *split;
211  
212      split = strchr(arg, '=');
213      if (split == NULL) {
214          err = usage_error("-v requires an argument of the form var=value");
215          goto out;
216      }
217  
218      *split = '\0';
219      split++;
220  
221      err = atf_map_insert(config, arg, split, false);
222  
223  out:
224      return err;
225  }
226  
227  static
228  atf_error_t
replace_path_param(atf_fs_path_t * param,const char * value)229  replace_path_param(atf_fs_path_t *param, const char *value)
230  {
231      atf_error_t err;
232      atf_fs_path_t temp;
233  
234      err = atf_fs_path_init_fmt(&temp, "%s", value);
235      if (!atf_is_error(err)) {
236          atf_fs_path_fini(param);
237          *param = temp;
238      }
239  
240      return err;
241  }
242  
243  /* ---------------------------------------------------------------------
244   * Test case listing.
245   * --------------------------------------------------------------------- */
246  
247  static
248  void
list_tcs(const atf_tp_t * tp)249  list_tcs(const atf_tp_t *tp)
250  {
251      const atf_tc_t *const *tcs;
252      const atf_tc_t *const *tcsptr;
253  
254      printf("Content-Type: application/X-atf-tp; version=\"1\"\n\n");
255  
256      tcs = atf_tp_get_tcs(tp);
257      INV(tcs != NULL);  /* Should be checked. */
258      for (tcsptr = tcs; *tcsptr != NULL; tcsptr++) {
259          const atf_tc_t *tc = *tcsptr;
260          char **vars = atf_tc_get_md_vars(tc);
261          char **ptr;
262  
263          INV(vars != NULL);  /* Should be checked. */
264  
265          if (tcsptr != tcs)  /* Not first. */
266              printf("\n");
267  
268          for (ptr = vars; *ptr != NULL; ptr += 2) {
269              if (strcmp(*ptr, "ident") == 0) {
270                  printf("ident: %s\n", *(ptr + 1));
271                  break;
272              }
273          }
274  
275          for (ptr = vars; *ptr != NULL; ptr += 2) {
276              if (strcmp(*ptr, "ident") != 0) {
277                  printf("%s: %s\n", *ptr, *(ptr + 1));
278              }
279          }
280  
281          atf_utils_free_charpp(vars);
282      }
283  }
284  
285  /* ---------------------------------------------------------------------
286   * Main.
287   * --------------------------------------------------------------------- */
288  
289  static
290  atf_error_t
handle_tcarg(const char * tcarg,char ** tcname,enum tc_part * tcpart)291  handle_tcarg(const char *tcarg, char **tcname, enum tc_part *tcpart)
292  {
293      atf_error_t err;
294  
295      err = atf_no_error();
296  
297      *tcname = strdup(tcarg);
298      if (*tcname == NULL) {
299          err = atf_no_memory_error();
300          goto out;
301      }
302  
303      char *delim = strchr(*tcname, ':');
304      if (delim != NULL) {
305          *delim = '\0';
306  
307          delim++;
308          if (strcmp(delim, "body") == 0) {
309              *tcpart = BODY;
310          } else if (strcmp(delim, "cleanup") == 0) {
311              *tcpart = CLEANUP;
312          } else {
313              err = usage_error("Invalid test case part `%s'", delim);
314              goto out;
315          }
316      }
317  
318  out:
319      return err;
320  }
321  
322  static
323  atf_error_t
process_params(int argc,char ** argv,struct params * p)324  process_params(int argc, char **argv, struct params *p)
325  {
326      atf_error_t err;
327      int ch;
328      int old_opterr;
329  
330      err = params_init(p, argv[0]);
331      if (atf_is_error(err))
332          goto out;
333  
334      old_opterr = opterr;
335      opterr = 0;
336      while (!atf_is_error(err) &&
337             (ch = getopt(argc, argv, GETOPT_POSIX ":lr:s:v:")) != -1) {
338          switch (ch) {
339          case 'l':
340              p->m_do_list = true;
341              break;
342  
343          case 'r':
344              err = replace_path_param(&p->m_resfile, optarg);
345              break;
346  
347          case 's':
348              err = replace_path_param(&p->m_srcdir, optarg);
349              break;
350  
351          case 'v':
352              err = parse_vflag(optarg, &p->m_config);
353              break;
354  
355          case ':':
356              err = usage_error("Option -%c requires an argument.", optopt);
357              break;
358  
359          case '?':
360          default:
361              err = usage_error("Unknown option -%c.", optopt);
362          }
363      }
364      argc -= optind;
365      argv += optind;
366  
367      /* Clear getopt state just in case the test wants to use it. */
368      opterr = old_opterr;
369      optind = 1;
370  #if defined(HAVE_OPTRESET)
371      optreset = 1;
372  #endif
373  
374      if (!atf_is_error(err)) {
375          if (p->m_do_list) {
376              if (argc > 0)
377                  err = usage_error("Cannot provide test case names with -l");
378          } else {
379              if (argc == 0)
380                  err = usage_error("Must provide a test case name");
381              else if (argc == 1)
382                  err = handle_tcarg(argv[0], &p->m_tcname, &p->m_tcpart);
383              else if (argc > 1) {
384                  err = usage_error("Cannot provide more than one test case "
385                                    "name");
386              }
387          }
388      }
389  
390      if (atf_is_error(err))
391          params_fini(p);
392  
393  out:
394      return err;
395  }
396  
397  static
398  atf_error_t
srcdir_strip_libtool(atf_fs_path_t * srcdir)399  srcdir_strip_libtool(atf_fs_path_t *srcdir)
400  {
401      atf_error_t err;
402      atf_fs_path_t parent;
403  
404      err = atf_fs_path_branch_path(srcdir, &parent);
405      if (atf_is_error(err))
406          goto out;
407  
408      atf_fs_path_fini(srcdir);
409      *srcdir = parent;
410  
411      INV(!atf_is_error(err));
412  out:
413      return err;
414  }
415  
416  static
417  atf_error_t
handle_srcdir(struct params * p)418  handle_srcdir(struct params *p)
419  {
420      atf_error_t err;
421      atf_dynstr_t leafname;
422      atf_fs_path_t exe, srcdir;
423      bool b;
424  
425      err = atf_fs_path_copy(&srcdir, &p->m_srcdir);
426      if (atf_is_error(err))
427          goto out;
428  
429      if (!atf_fs_path_is_absolute(&srcdir)) {
430          atf_fs_path_t srcdirabs;
431  
432          err = atf_fs_path_to_absolute(&srcdir, &srcdirabs);
433          if (atf_is_error(err))
434              goto out_srcdir;
435  
436          atf_fs_path_fini(&srcdir);
437          srcdir = srcdirabs;
438      }
439  
440      err = atf_fs_path_leaf_name(&srcdir, &leafname);
441      if (atf_is_error(err))
442          goto out_srcdir;
443      else {
444          const bool libs = atf_equal_dynstr_cstring(&leafname, ".libs");
445          atf_dynstr_fini(&leafname);
446  
447          if (libs) {
448              err = srcdir_strip_libtool(&srcdir);
449              if (atf_is_error(err))
450                  goto out;
451          }
452      }
453  
454      err = atf_fs_path_copy(&exe, &srcdir);
455      if (atf_is_error(err))
456          goto out_srcdir;
457  
458      err = atf_fs_path_append_fmt(&exe, "%s", progname);
459      if (atf_is_error(err))
460          goto out_exe;
461  
462      err = atf_fs_exists(&exe, &b);
463      if (!atf_is_error(err)) {
464          if (b) {
465              err = atf_map_insert(&p->m_config, "srcdir",
466                                   strdup(atf_fs_path_cstring(&srcdir)), true);
467          } else {
468              err = user_error("Cannot find the test program in the source "
469                               "directory `%s'", atf_fs_path_cstring(&srcdir));
470          }
471      }
472  
473  out_exe:
474      atf_fs_path_fini(&exe);
475  out_srcdir:
476      atf_fs_path_fini(&srcdir);
477  out:
478      return err;
479  }
480  
481  static
482  atf_error_t
run_tc(const atf_tp_t * tp,struct params * p,int * exitcode)483  run_tc(const atf_tp_t *tp, struct params *p, int *exitcode)
484  {
485      atf_error_t err;
486  
487      err = atf_no_error();
488  
489      if (!atf_tp_has_tc(tp, p->m_tcname)) {
490          err = usage_error("Unknown test case `%s'", p->m_tcname);
491          goto out;
492      }
493  
494      if (!atf_env_has("__RUNNING_INSIDE_ATF_RUN") || strcmp(atf_env_get(
495          "__RUNNING_INSIDE_ATF_RUN"), "internal-yes-value") != 0)
496      {
497          print_warning("Running test cases outside of kyua(1) is unsupported");
498          print_warning("No isolation nor timeout control is being applied; you "
499                        "may get unexpected failures; see atf-test-case(4)");
500      }
501  
502      switch (p->m_tcpart) {
503      case BODY:
504          err = atf_tp_run(tp, p->m_tcname, atf_fs_path_cstring(&p->m_resfile));
505          if (atf_is_error(err)) {
506              /* TODO: Handle error */
507              *exitcode = EXIT_FAILURE;
508              atf_error_free(err);
509          } else {
510              *exitcode = EXIT_SUCCESS;
511          }
512  
513          break;
514  
515      case CLEANUP:
516          err = atf_tp_cleanup(tp, p->m_tcname);
517          if (atf_is_error(err)) {
518              /* TODO: Handle error */
519              *exitcode = EXIT_FAILURE;
520              atf_error_free(err);
521          } else {
522              *exitcode = EXIT_SUCCESS;
523          }
524  
525          break;
526  
527      default:
528          UNREACHABLE;
529      }
530  
531      INV(!atf_is_error(err));
532  out:
533      return err;
534  }
535  
536  static
537  atf_error_t
controlled_main(int argc,char ** argv,atf_error_t (* add_tcs_hook)(atf_tp_t *),int * exitcode)538  controlled_main(int argc, char **argv,
539                  atf_error_t (*add_tcs_hook)(atf_tp_t *),
540                  int *exitcode)
541  {
542      atf_error_t err;
543      struct params p;
544      atf_tp_t tp;
545      char **raw_config;
546  
547      err = process_params(argc, argv, &p);
548      if (atf_is_error(err))
549          goto out;
550  
551      err = handle_srcdir(&p);
552      if (atf_is_error(err))
553          goto out_p;
554  
555      raw_config = atf_map_to_charpp(&p.m_config);
556      if (raw_config == NULL) {
557          err = atf_no_memory_error();
558          goto out_p;
559      }
560      err = atf_tp_init(&tp, (const char* const*)raw_config);
561      atf_utils_free_charpp(raw_config);
562      if (atf_is_error(err))
563          goto out_p;
564  
565      err = add_tcs_hook(&tp);
566      if (atf_is_error(err))
567          goto out_tp;
568  
569      if (p.m_do_list) {
570          list_tcs(&tp);
571          INV(!atf_is_error(err));
572          *exitcode = EXIT_SUCCESS;
573      } else {
574          err = run_tc(&tp, &p, exitcode);
575      }
576  
577  out_tp:
578      atf_tp_fini(&tp);
579  out_p:
580      params_fini(&p);
581  out:
582      return err;
583  }
584  
585  int
atf_tp_main(int argc,char ** argv,atf_error_t (* add_tcs_hook)(atf_tp_t *))586  atf_tp_main(int argc, char **argv, atf_error_t (*add_tcs_hook)(atf_tp_t *))
587  {
588      atf_error_t err;
589      int exitcode;
590  
591      progname = strrchr(argv[0], '/');
592      if (progname == NULL)
593          progname = argv[0];
594      else
595          progname++;
596  
597      /* Libtool workaround: if running from within the source tree (binaries
598       * that are not installed yet), skip the "lt-" prefix added to files in
599       * the ".libs" directory to show the real (not temporary) name. */
600      if (strncmp(progname, "lt-", 3) == 0)
601          progname += 3;
602  
603      exitcode = EXIT_FAILURE; /* Silence GCC warning. */
604      err = controlled_main(argc, argv, add_tcs_hook, &exitcode);
605      if (atf_is_error(err)) {
606          print_error(err);
607          atf_error_free(err);
608          exitcode = EXIT_FAILURE;
609      }
610  
611      return exitcode;
612  }
613