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    static \
92    atf_error_t \
93    name ## _error(const char *fmt, ...) \
94    { \
95        atf_error_t err; \
96        struct name ## _error_data data; \
97        va_list ap; \
98        \
99        va_start(ap, fmt); \
100        vsnprintf(data.m_what, sizeof(data.m_what), fmt, ap); \
101        va_end(ap); \
102        \
103        err = atf_error_new(#name, &data, sizeof(data), name ## _format); \
104        \
105        return err; \
106    }
107
108FREE_FORM_ERROR(usage);
109FREE_FORM_ERROR(user);
110
111/* ---------------------------------------------------------------------
112 * Printing functions.
113 * --------------------------------------------------------------------- */
114
115static
116void
117print_error(const atf_error_t err)
118{
119    char buf[4096];
120
121    PRE(atf_is_error(err));
122
123    atf_error_format(err, buf, sizeof(buf));
124    fprintf(stderr, "%s: ERROR: %s\n", progname, buf);
125
126    if (atf_error_is(err, "usage"))
127        fprintf(stderr, "%s: See atf-test-program(1) for usage details.\n",
128                progname);
129}
130
131static
132void
133print_warning(const char *message)
134{
135    fprintf(stderr, "%s: WARNING: %s\n", progname, message);
136}
137
138/* ---------------------------------------------------------------------
139 * Options handling.
140 * --------------------------------------------------------------------- */
141
142struct params {
143    bool m_do_list;
144    atf_fs_path_t m_srcdir;
145    char *m_tcname;
146    enum tc_part m_tcpart;
147    atf_fs_path_t m_resfile;
148    atf_map_t m_config;
149};
150
151static
152atf_error_t
153argv0_to_dir(const char *argv0, atf_fs_path_t *dir)
154{
155    atf_error_t err;
156    atf_fs_path_t temp;
157
158    err = atf_fs_path_init_fmt(&temp, "%s", argv0);
159    if (atf_is_error(err))
160        goto out;
161
162    err = atf_fs_path_branch_path(&temp, dir);
163
164    atf_fs_path_fini(&temp);
165out:
166    return err;
167}
168
169static
170atf_error_t
171params_init(struct params *p, const char *argv0)
172{
173    atf_error_t err;
174
175    p->m_do_list = false;
176    p->m_tcname = NULL;
177    p->m_tcpart = BODY;
178
179    err = argv0_to_dir(argv0, &p->m_srcdir);
180    if (atf_is_error(err))
181        return err;
182
183    err = atf_fs_path_init_fmt(&p->m_resfile, "/dev/stdout");
184    if (atf_is_error(err)) {
185        atf_fs_path_fini(&p->m_srcdir);
186        return err;
187    }
188
189    err = atf_map_init(&p->m_config);
190    if (atf_is_error(err)) {
191        atf_fs_path_fini(&p->m_resfile);
192        atf_fs_path_fini(&p->m_srcdir);
193        return err;
194    }
195
196    return err;
197}
198
199static
200void
201params_fini(struct params *p)
202{
203    atf_map_fini(&p->m_config);
204    atf_fs_path_fini(&p->m_resfile);
205    atf_fs_path_fini(&p->m_srcdir);
206    if (p->m_tcname != NULL)
207        free(p->m_tcname);
208}
209
210static
211atf_error_t
212parse_vflag(char *arg, atf_map_t *config)
213{
214    atf_error_t err;
215    char *split;
216
217    split = strchr(arg, '=');
218    if (split == NULL) {
219        err = usage_error("-v requires an argument of the form var=value");
220        goto out;
221    }
222
223    *split = '\0';
224    split++;
225
226    err = atf_map_insert(config, arg, split, false);
227
228out:
229    return err;
230}
231
232static
233atf_error_t
234replace_path_param(atf_fs_path_t *param, const char *value)
235{
236    atf_error_t err;
237    atf_fs_path_t temp;
238
239    err = atf_fs_path_init_fmt(&temp, "%s", value);
240    if (!atf_is_error(err)) {
241        atf_fs_path_fini(param);
242        *param = temp;
243    }
244
245    return err;
246}
247
248/* ---------------------------------------------------------------------
249 * Test case listing.
250 * --------------------------------------------------------------------- */
251
252static
253void
254list_tcs(const atf_tp_t *tp)
255{
256    const atf_tc_t *const *tcs;
257    const atf_tc_t *const *tcsptr;
258
259    printf("Content-Type: application/X-atf-tp; version=\"1\"\n\n");
260
261    tcs = atf_tp_get_tcs(tp);
262    INV(tcs != NULL);  /* Should be checked. */
263    for (tcsptr = tcs; *tcsptr != NULL; tcsptr++) {
264        const atf_tc_t *tc = *tcsptr;
265        char **vars = atf_tc_get_md_vars(tc);
266        char **ptr;
267
268        INV(vars != NULL);  /* Should be checked. */
269
270        if (tcsptr != tcs)  /* Not first. */
271            printf("\n");
272
273        for (ptr = vars; *ptr != NULL; ptr += 2) {
274            if (strcmp(*ptr, "ident") == 0) {
275                printf("ident: %s\n", *(ptr + 1));
276                break;
277            }
278        }
279
280        for (ptr = vars; *ptr != NULL; ptr += 2) {
281            if (strcmp(*ptr, "ident") != 0) {
282                printf("%s: %s\n", *ptr, *(ptr + 1));
283            }
284        }
285
286        atf_utils_free_charpp(vars);
287    }
288}
289
290/* ---------------------------------------------------------------------
291 * Main.
292 * --------------------------------------------------------------------- */
293
294static
295atf_error_t
296handle_tcarg(const char *tcarg, char **tcname, enum tc_part *tcpart)
297{
298    atf_error_t err;
299
300    err = atf_no_error();
301
302    *tcname = strdup(tcarg);
303    if (*tcname == NULL) {
304        err = atf_no_memory_error();
305        goto out;
306    }
307
308    char *delim = strchr(*tcname, ':');
309    if (delim != NULL) {
310        *delim = '\0';
311
312        delim++;
313        if (strcmp(delim, "body") == 0) {
314            *tcpart = BODY;
315        } else if (strcmp(delim, "cleanup") == 0) {
316            *tcpart = CLEANUP;
317        } else {
318            err = usage_error("Invalid test case part `%s'", delim);
319            goto out;
320        }
321    }
322
323out:
324    return err;
325}
326
327static
328atf_error_t
329process_params(int argc, char **argv, struct params *p)
330{
331    atf_error_t err;
332    int ch;
333    int old_opterr;
334
335    err = params_init(p, argv[0]);
336    if (atf_is_error(err))
337        goto out;
338
339    old_opterr = opterr;
340    opterr = 0;
341    while (!atf_is_error(err) &&
342           (ch = getopt(argc, argv, GETOPT_POSIX ":lr:s:v:")) != -1) {
343        switch (ch) {
344        case 'l':
345            p->m_do_list = true;
346            break;
347
348        case 'r':
349            err = replace_path_param(&p->m_resfile, optarg);
350            break;
351
352        case 's':
353            err = replace_path_param(&p->m_srcdir, optarg);
354            break;
355
356        case 'v':
357            err = parse_vflag(optarg, &p->m_config);
358            break;
359
360        case ':':
361            err = usage_error("Option -%c requires an argument.", optopt);
362            break;
363
364        case '?':
365        default:
366            err = usage_error("Unknown option -%c.", optopt);
367        }
368    }
369    argc -= optind;
370    argv += optind;
371
372    /* Clear getopt state just in case the test wants to use it. */
373    opterr = old_opterr;
374    optind = 1;
375#if defined(HAVE_OPTRESET)
376    optreset = 1;
377#endif
378
379    if (!atf_is_error(err)) {
380        if (p->m_do_list) {
381            if (argc > 0)
382                err = usage_error("Cannot provide test case names with -l");
383        } else {
384            if (argc == 0)
385                err = usage_error("Must provide a test case name");
386            else if (argc == 1)
387                err = handle_tcarg(argv[0], &p->m_tcname, &p->m_tcpart);
388            else if (argc > 1) {
389                err = usage_error("Cannot provide more than one test case "
390                                  "name");
391            }
392        }
393    }
394
395    if (atf_is_error(err))
396        params_fini(p);
397
398out:
399    return err;
400}
401
402static
403atf_error_t
404srcdir_strip_libtool(atf_fs_path_t *srcdir)
405{
406    atf_error_t err;
407    atf_fs_path_t parent;
408
409    err = atf_fs_path_branch_path(srcdir, &parent);
410    if (atf_is_error(err))
411        goto out;
412
413    atf_fs_path_fini(srcdir);
414    *srcdir = parent;
415
416    INV(!atf_is_error(err));
417out:
418    return err;
419}
420
421static
422atf_error_t
423handle_srcdir(struct params *p)
424{
425    atf_error_t err;
426    atf_dynstr_t leafname;
427    atf_fs_path_t exe, srcdir;
428    bool b;
429
430    err = atf_fs_path_copy(&srcdir, &p->m_srcdir);
431    if (atf_is_error(err))
432        goto out;
433
434    if (!atf_fs_path_is_absolute(&srcdir)) {
435        atf_fs_path_t srcdirabs;
436
437        err = atf_fs_path_to_absolute(&srcdir, &srcdirabs);
438        if (atf_is_error(err))
439            goto out_srcdir;
440
441        atf_fs_path_fini(&srcdir);
442        srcdir = srcdirabs;
443    }
444
445    err = atf_fs_path_leaf_name(&srcdir, &leafname);
446    if (atf_is_error(err))
447        goto out_srcdir;
448    else {
449        const bool libs = atf_equal_dynstr_cstring(&leafname, ".libs");
450        atf_dynstr_fini(&leafname);
451
452        if (libs) {
453            err = srcdir_strip_libtool(&srcdir);
454            if (atf_is_error(err))
455                goto out;
456        }
457    }
458
459    err = atf_fs_path_copy(&exe, &srcdir);
460    if (atf_is_error(err))
461        goto out_srcdir;
462
463    err = atf_fs_path_append_fmt(&exe, "%s", progname);
464    if (atf_is_error(err))
465        goto out_exe;
466
467    err = atf_fs_exists(&exe, &b);
468    if (!atf_is_error(err)) {
469        if (b) {
470            err = atf_map_insert(&p->m_config, "srcdir",
471                                 strdup(atf_fs_path_cstring(&srcdir)), true);
472        } else {
473            err = user_error("Cannot find the test program in the source "
474                             "directory `%s'", atf_fs_path_cstring(&srcdir));
475        }
476    }
477
478out_exe:
479    atf_fs_path_fini(&exe);
480out_srcdir:
481    atf_fs_path_fini(&srcdir);
482out:
483    return err;
484}
485
486static
487atf_error_t
488run_tc(const atf_tp_t *tp, struct params *p, int *exitcode)
489{
490    atf_error_t err;
491
492    err = atf_no_error();
493
494    if (!atf_tp_has_tc(tp, p->m_tcname)) {
495        err = usage_error("Unknown test case `%s'", p->m_tcname);
496        goto out;
497    }
498
499    if (!atf_env_has("__RUNNING_INSIDE_ATF_RUN") || strcmp(atf_env_get(
500        "__RUNNING_INSIDE_ATF_RUN"), "internal-yes-value") != 0)
501    {
502        print_warning("Running test cases without atf-run(1) is unsupported");
503        print_warning("No isolation nor timeout control is being applied; you "
504                      "may get unexpected failures; see atf-test-case(4)");
505    }
506
507    switch (p->m_tcpart) {
508    case BODY:
509        err = atf_tp_run(tp, p->m_tcname, atf_fs_path_cstring(&p->m_resfile));
510        if (atf_is_error(err)) {
511            /* TODO: Handle error */
512            *exitcode = EXIT_FAILURE;
513            atf_error_free(err);
514        } else {
515            *exitcode = EXIT_SUCCESS;
516        }
517
518        break;
519
520    case CLEANUP:
521        err = atf_tp_cleanup(tp, p->m_tcname);
522        if (atf_is_error(err)) {
523            /* TODO: Handle error */
524            *exitcode = EXIT_FAILURE;
525            atf_error_free(err);
526        } else {
527            *exitcode = EXIT_SUCCESS;
528        }
529
530        break;
531
532    default:
533        UNREACHABLE;
534    }
535
536    INV(!atf_is_error(err));
537out:
538    return err;
539}
540
541static
542atf_error_t
543controlled_main(int argc, char **argv,
544                atf_error_t (*add_tcs_hook)(atf_tp_t *),
545                int *exitcode)
546{
547    atf_error_t err;
548    struct params p;
549    atf_tp_t tp;
550    char **raw_config;
551
552    err = process_params(argc, argv, &p);
553    if (atf_is_error(err))
554        goto out;
555
556    err = handle_srcdir(&p);
557    if (atf_is_error(err))
558        goto out_p;
559
560    raw_config = atf_map_to_charpp(&p.m_config);
561    if (raw_config == NULL) {
562        err = atf_no_memory_error();
563        goto out_p;
564    }
565    err = atf_tp_init(&tp, (const char* const*)raw_config);
566    atf_utils_free_charpp(raw_config);
567    if (atf_is_error(err))
568        goto out_p;
569
570    err = add_tcs_hook(&tp);
571    if (atf_is_error(err))
572        goto out_tp;
573
574    if (p.m_do_list) {
575        list_tcs(&tp);
576        INV(!atf_is_error(err));
577        *exitcode = EXIT_SUCCESS;
578    } else {
579        err = run_tc(&tp, &p, exitcode);
580    }
581
582out_tp:
583    atf_tp_fini(&tp);
584out_p:
585    params_fini(&p);
586out:
587    return err;
588}
589
590int
591atf_tp_main(int argc, char **argv, atf_error_t (*add_tcs_hook)(atf_tp_t *))
592{
593    atf_error_t err;
594    int exitcode;
595
596    progname = strrchr(argv[0], '/');
597    if (progname == NULL)
598        progname = argv[0];
599    else
600        progname++;
601
602    /* Libtool workaround: if running from within the source tree (binaries
603     * that are not installed yet), skip the "lt-" prefix added to files in
604     * the ".libs" directory to show the real (not temporary) name. */
605    if (strncmp(progname, "lt-", 3) == 0)
606        progname += 3;
607
608    exitcode = EXIT_FAILURE; /* Silence GCC warning. */
609    err = controlled_main(argc, argv, add_tcs_hook, &exitcode);
610    if (atf_is_error(err)) {
611        print_error(err);
612        atf_error_free(err);
613        exitcode = EXIT_FAILURE;
614    }
615
616    return exitcode;
617}
618