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