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