vinyl-cache/bin/vinyltest/vtest2/src/vtc_misc.c
0
/*-
1
 * Copyright (c) 2008-2011 Varnish Software AS
2
 * All rights reserved.
3
 *
4
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
5
 *
6
 * SPDX-License-Identifier: BSD-2-Clause
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions
10
 * are met:
11
 * 1. Redistributions of source code must retain the above copyright
12
 *    notice, this list of conditions and the following disclaimer.
13
 * 2. Redistributions in binary form must reproduce the above copyright
14
 *    notice, this list of conditions and the following disclaimer in the
15
 *    documentation and/or other materials provided with the distribution.
16
 *
17
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
21
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27
 * SUCH DAMAGE.
28
 */
29
30
#include "config.h"
31
32
#include <sys/wait.h>
33
#include <sys/socket.h>
34
35
#include <grp.h>
36
#include <math.h>
37
#include <pwd.h>
38
#include <stdio.h>
39
#include <stdlib.h>
40
#include <string.h>
41
#include <sys/un.h>
42
#include <unistd.h>
43
44
#ifdef HAVE_SYS_PERSONALITY_H
45
#  include <sys/personality.h>
46
#endif
47
48
#include "vtc.h"
49
50
#include "vfil.h"
51
#include "vnum.h"
52
#include "vre.h"
53
#include "vtcp.h"
54
#include "vsa.h"
55
#include "vss.h"
56
#include "vtim.h"
57
#include "vus.h"
58
59
/* SECTION: vtest vtest
60
 *
61
 * This should be the first command in your vtc as it will identify the test
62
 * case with a short yet descriptive sentence. It takes exactly one argument, a
63
 * string, eg::
64
 *
65
 *         vtest "Check that vtest is actually a valid command"
66
 *
67
 * It will also print that string in the log.
68
 */
69
70
void v_matchproto_(cmd_f)
71 46536
cmd_vtest(CMD_ARGS)
72
{
73
74 46536
        (void)priv;
75 46536
        (void)vl;
76 23331
77 23205
        if (av == NULL)
78
                return;
79 23205
        AZ(strcmp(av[0], "vtest"));
80 23205
81 23205
        vtc_log(vl, 1, "VTEST %s", av[1]);
82 46536
        AZ(av[2]);
83
}
84
85
/* SECTION: varnishtest varnishtest
86
 *
87
 * Alternate name for 'vtest', see above.
88
 *
89
 */
90
91 23457
void v_matchproto_(cmd_f)
92
cmd_varnishtest(CMD_ARGS)
93
{
94 23457
95 23457
        (void)priv;
96 23331
        (void)vl;
97 126
98
        if (av == NULL)
99 126
                return;
100 126
        AZ(strcmp(av[0], "varnishtest"));
101 126
102 23457
        vtc_log(vl, 1, "VTEST %s", av[1]);
103
        AZ(av[2]);
104
}
105
106
/* SECTION: shell shell
107
 *
108
 * NOTE: This command is available everywhere commands are given.
109
 *
110
 * Pass the string given as argument to a shell. If you have multiple
111
 * commands to run, you can use curly brackets to describe a multi-lines
112
 * script, eg::
113
 *
114
 *         shell {
115
 *                 echo begin
116
 *                 cat /etc/fstab
117
 *                 echo end
118
 *         }
119
 *
120
 * By default a zero exit code is expected, otherwise the vtc will fail.
121
 *
122
 * Notice that the commandstring is prefixed with "exec 2>&1;" to combine
123
 * stderr and stdout back to the test process.
124
 *
125
 * Optional arguments:
126
 *
127
 * \-err
128
 *      Expect non-zero exit code.
129
 *
130
 * \-exit N
131
 *      Expect exit code N instead of zero.
132
 *
133
 * \-expect STRING
134
 *      Expect string to be found in stdout+err.
135
 *
136
 * \-match REGEXP
137
 *      Expect regexp to match the stdout+err output.
138
 */
139
/* SECTION: client-server.spec.shell
140
 *
141
 * shell
142
 *      Same as for the top-level shell.
143
 */
144 10983
145
static void
146
cmd_shell_engine(struct vtclog *vl, int ok, const char *cmd,
147
    const char *expect, const char *re)
148
{
149 10983
        struct vsb *vsb, re_vsb[1];
150
        FILE *fp;
151
        vre_t *vre = NULL;
152
        int r, c;
153
        int err, erroff;
154 10983
        char errbuf[VRE_ERROR_LEN];
155 10983
156 10983
        AN(vl);
157 10983
        AN(cmd);
158 10983
        vsb = VSB_new_auto();
159 1806
        AN(vsb);
160 1806
        if (re != NULL) {
161 0
                vre = VRE_compile(re, 0, &err, &erroff, 1);
162 0
                if (vre == NULL) {
163 0
                        AN(VSB_init(re_vsb, errbuf, sizeof errbuf));
164 0
                        AZ(VRE_error(re_vsb, err));
165 0
                        AZ(VSB_finish(re_vsb));
166
                        VSB_fini(re_vsb);
167 0
                        vtc_fatal(vl,
168
                            "shell_match invalid regexp (\"%s\" at %d)",
169 1806
                            errbuf, erroff);
170 10983
                }
171 10983
        }
172 10983
        VSB_printf(vsb, "set -e ;");
173 10983
        VSB_printf(vsb, "exec 2>&1 ; %s", cmd);
174 10983
        AZ(VSB_finish(vsb));
175 10983
        vtc_dump(vl, 4, "shell_cmd", VSB_data(vsb), -1);
176 0
        fp = popen(VSB_data(vsb), "r");
177 10983
        if (fp == NULL)
178 10983
                vtc_fatal(vl, "popen fails: %s", strerror(errno));
179 17171919
        VSB_clear(vsb);
180 17171919
        do {
181 17160936
                c = getc(fp);
182 17171919
                if (c != EOF)
183 10983
                        VSB_putc(vsb, c);
184 10983
        } while (c != EOF);
185 10983
        r = pclose(fp);
186 10983
        AZ(VSB_finish(vsb));
187 10983
        vtc_dump(vl, 4, "shell_out", VSB_data(vsb), VSB_len(vsb));
188 0
        vtc_log(vl, 4, "shell_status = 0x%04x", WEXITSTATUS(r));
189
        if (WIFSIGNALED(r))
190 10983
                vtc_log(vl, 4, "shell_signal = %d", WTERMSIG(r));
191 0
192 10983
        if (ok < 0 && !WEXITSTATUS(r) && !WIFSIGNALED(r))
193 84
                vtc_fatal(vl, "shell did not fail as expected");
194 42
        else if (ok >= 0 && WEXITSTATUS(r) != ok)
195
                vtc_fatal(vl, "shell_exit not as expected: "
196 10941
                    "got 0x%04x wanted 0x%04x", WEXITSTATUS(r), ok);
197 5103
198 0
        if (expect != NULL) {
199 0
                if (strstr(VSB_data(vsb), expect) == NULL)
200
                        vtc_fatal(vl,
201 5103
                            "shell_expect not found: (\"%s\")", expect);
202 10941
                else
203 1806
                        vtc_log(vl, 4, "shell_expect found");
204 0
        } else if (vre != NULL) {
205
                if (VRE_match(vre, VSB_data(vsb), VSB_len(vsb), 0, NULL) < 1)
206 1806
                        vtc_fatal(vl, "shell_match failed: (\"%s\")", re);
207 1806
                else
208 1806
                        vtc_log(vl, 4, "shell_match succeeded");
209 10941
                VRE_free(&vre);
210 10941
        }
211
        VSB_destroy(&vsb);
212
}
213
214 34314
215
void
216 34314
cmd_shell(CMD_ARGS)
217 34314
{
218
        const char *expect = NULL;
219 34314
        const char *re = NULL;
220
        int n;
221 34314
        int ok = 0;
222
223 34314
        (void)priv;
224 23331
225 22827
        if (av == NULL)
226 22827
                return;
227 4452
        for (n = 1; av[n] != NULL; n++) {
228 22827
                if (!strcmp(av[n], "-err")) {
229 483
                        ok = -1;
230 483
                } else if (!strcmp(av[n], "-exit")) {
231 18375
                        n += 1;
232 5103
                        ok = atoi(av[n]);
233 0
                } else if (!strcmp(av[n], "-expect")) {
234
                        if (re != NULL)
235 5103
                                vtc_fatal(vl,
236 5103
                                    "Cannot use -expect with -match");
237 17892
                        n += 1;
238 1806
                        expect = av[n];
239 0
                } else if (!strcmp(av[n], "-match")) {
240
                        if (expect != NULL)
241 1806
                                vtc_fatal(vl,
242 1806
                                    "Cannot use -match with -expect");
243 1806
                        n += 1;
244 10983
                        re = av[n];
245
                } else {
246 11844
                        break;
247 10983
                }
248 10983
        }
249 34314
        AN(av[n]);
250
        cmd_shell_engine(vl, ok, av[n], expect, re);
251
}
252
253
/* SECTION: filewrite filewrite
254
 *
255
 * Write strings to file
256
 *
257
 *         filewrite [-a] /somefile "Hello" " " "World\n"
258
 *
259
 * The -a flag opens the file in append mode.
260
 *
261
 */
262 24066
263
void v_matchproto_(cmd_f)
264
cmd_filewrite(CMD_ARGS)
265
{
266 24066
        FILE *fo;
267
        int n;
268 24066
        const char *mode = "w";
269
270 24066
        (void)priv;
271 23331
272 735
        if (av == NULL)
273 357
                return;
274 357
        if (av[1] != NULL && !strcmp(av[1], "-a")) {
275 357
                av++;
276 735
                mode = "a";
277 0
        }
278 735
        if (av[1] == NULL)
279 735
                vtc_fatal(vl, "Need filename");
280 0
        fo = fopen(av[1], mode);
281 1764
        if (fo == NULL)
282 1029
                vtc_fatal(vl, "Cannot open %s: %s", av[1], strerror(errno));
283 735
        for (n = 2; av[n] != NULL; n++)
284 24066
                (void)fputs(av[n], fo);
285
        AZ(fclose(fo));
286
}
287
288
/* SECTION: setenv setenv
289
 *
290
 * Set or change an environment variable::
291
 *
292
 *         setenv FOO "bar baz"
293
 *
294
 * The above will set the environment variable $FOO to the value
295
 * provided. There is also an ``-ifunset`` argument which will only
296
 * set the value if the environment variable does not already
297
 * exist::
298
 *
299
 *        setenv -ifunset FOO quux
300
 */
301 23478
302
void v_matchproto_(cmd_f)
303
cmd_setenv(CMD_ARGS)
304
{
305
        int r;
306 23478
        int force;
307
308 23478
        (void)priv;
309 23331
310 147
        if (av == NULL)
311 147
                return;
312
        AN(av[1]);
313 147
        AN(av[2]);
314 147
315 42
        force = 1;
316 42
        if (strcmp("-ifunset", av[1]) == 0) {
317 42
                force = 0;
318 42
                av++;
319 147
                AN(av[2]);
320 147
        }
321 147
        if (av[3] != NULL)
322 0
                vtc_fatal(vl, "CMD setenv: Unexpected argument '%s'", av[3]);
323 0
        r = setenv(av[1], av[2], force);
324 23478
        if (r != 0)
325
                vtc_log(vl, 0, "CMD setenv %s=\"%s\" failed: %s",
326
                    av[1], av[2], strerror(errno));
327
}
328
329
/* SECTION: delay delay
330
 *
331
 * NOTE: This command is available everywhere commands are given.
332
 *
333
 * Sleep for the number of seconds specified in the argument. The number
334 38549
 * can include a fractional part, e.g. 1.5.
335
 */
336
void
337
cmd_delay(CMD_ARGS)
338 38549
{
339 38549
        double f;
340 23331
341 15218
        (void)priv;
342 15218
        if (av == NULL)
343 15218
                return;
344 15218
        AN(av[1]);
345 0
        AZ(av[2]);
346 15218
        f = VNUM(av[1]);
347 15218
        if (isnan(f))
348 38549
                vtc_fatal(vl, "Syntax error in number (%s)", av[1]);
349
        vtc_log(vl, 3, "delaying %g second(s)", f);
350
        VTIM_sleep(f);
351
}
352
353
/* SECTION: include include
354
 *
355
 * Executes a vtc fragment::
356
 *
357
 *         include FILE [...]
358
 *
359
 * Open a file and execute it as a VTC fragment. This command is available
360
 * everywhere commands are given.
361 23415
 *
362
 */
363
void
364
cmd_include(CMD_ARGS)
365
{
366 23415
        char *spec;
367 23331
        unsigned i;
368
369 84
        if (av == NULL)
370 0
                return;
371
372 210
        if (av[1] == NULL)
373 126
                vtc_fatal(vl, "CMD include: At least 1 argument required");
374 126
375 0
        for (i = 1; av[i] != NULL; i++) {
376 0
                spec = VFIL_readfile(NULL, av[i], NULL);
377 126
                if (spec == NULL)
378 126
                        vtc_fatal(vl, "CMD include: Unable to read file '%s' "
379 126
                            "(%s)", av[i], strerror(errno));
380 126
                vtc_log(vl, 2, "Begin include '%s'", av[i]);
381 126
                parse_string(vl, priv, spec);
382 23415
                vtc_log(vl, 2, "End include '%s'", av[i]);
383
                free(spec);
384
        }
385
}
386
387
/**********************************************************************
388
 * Most test-cases use only numeric IP#'s but a few requires non-demented
389
 * DNS services.  This is a basic sanity check for those.
390 21
 */
391
392
static int
393
dns_works(void)
394
{
395
        const struct suckaddr *sa;
396 21
        char abuf[VTCP_ADDRBUFSIZE];
397
        char pbuf[VTCP_PORTBUFSIZE];
398 21
399 1
        sa = VSS_ResolveOne(NULL, "dns-canary.vinyl-cache.org", NULL,
400 20
            AF_INET, SOCK_STREAM, 0);
401 20
        if (sa == NULL)
402 20
                return (0);
403 0
        VTCP_name(sa, abuf, sizeof abuf, pbuf, sizeof pbuf);
404
        VSA_free(&sa);
405 20
        if (strcmp(abuf, "192.0.2.255"))
406
                return (0);
407 20
408 20
        sa = VSS_ResolveOne(NULL, "dns-canary.vinyl-cache.org", NULL,
409 0
            AF_INET6, SOCK_STREAM, 0);
410 0
        if (sa == NULL)
411 21
                return (1); /* the canary is ipv4 only */
412
        VSA_free(&sa);
413
        return (0);
414
}
415
416
/**********************************************************************
417
 * Test if IPv4/IPv6 works
418 231
 */
419
420
static int
421
ipvx_works(const char *target)
422
{
423 231
        const struct suckaddr *sa;
424 231
        int fd;
425 0
426 231
        sa = VSS_ResolveOne(NULL, target, "0", 0, SOCK_STREAM, 0);
427 231
        if (sa == NULL)
428 231
                return (0);
429 231
        fd = VTCP_bind(sa, NULL);
430 231
        VSA_free(&sa);
431
        if (fd >= 0) {
432 0
                VTCP_close(&fd);
433 231
                return (1);
434
        }
435
        return (0);
436
}
437
438 0
/**********************************************************************/
439
440 0
static int
441
addr_no_randomize_works(void)
442
{
443
        int r = 0;
444
445
#ifdef HAVE_SYS_PERSONALITY_H
446 0
        r = personality(0xffffffff);
447
        r = personality(r | ADDR_NO_RANDOMIZE);
448
#endif
449
        return (r >= 0);
450
}
451
452 21
/**********************************************************************/
453
454
static int
455 21
uds_socket(void *priv, const struct sockaddr_un *uds)
456
{
457
458 21
        return (VUS_bind(uds, priv));
459
}
460
static int
461
abstract_uds_works(void)
462
{
463 21
        const char *err;
464 21
        int fd;
465 21
466 0
        fd = VUS_resolver("@vtc.feature.abstract_uds", uds_socket, NULL, &err);
467 0
        if (fd < 0)
468 21
                return (0);
469
        AZ(close(fd));
470
        return (1);
471
}
472
473
/* SECTION: feature feature
474
 *
475
 * Test that the required feature(s) for a test are available, and skip
476
 * the test otherwise; or change the interpretation of the test, as
477
 * documented below. feature takes any number of arguments from this list:
478
 *
479
 * 64bit
480
 *        The environment is 64 bits
481
 * ipv4
482
 *        127.0.0.1 works
483
 * ipv6
484
 *        [::1] works
485
 * dns
486
 *        DNS lookups are working
487
 * topbuild
488
 *        The test has been started with '-i'
489
 * root
490
 *        The test has been invoked by the root user
491
 * user <username>
492
 *        The <username> user is present
493
 * group <groupname>
494
 *        The <groupname> group is present
495
 * user_varnish
496
 *        The varnish user is present
497
 * user_vinyl
498
 *        The vinyl user is present
499
 * user_vcache
500
 *        The vcache user is present
501
 * group_varnish
502
 *        The varnish group is present
503
 * group_vinyl
504
 *        The vinyl group is present
505
 * cmd <command-line>
506
 *        A command line that should execute with a zero exit status
507
 * ignore_unknown_macro
508
 *        Do not fail the test if a string of the form ${...} is not
509
 *        recognized as a macro.
510
 * persistent_storage
511
 *        Varnish was built with the deprecated persistent storage.
512
 * coverage
513
 *        Varnish was built with code coverage enabled.
514
 * asan
515
 *        Varnish was built with the address sanitizer.
516
 * msan
517
 *        Varnish was built with the memory sanitizer.
518
 * tsan
519
 *        Varnish was built with the thread sanitizer.
520
 * ubsan
521
 *        Varnish was built with the undefined behavior sanitizer.
522
 * sanitizer
523 25830
 *        Varnish was built with a sanitizer.
524
 * workspace_emulator
525
 *        Varnish was built with its workspace emulator.
526
 * abstract_uds
527
 *        Creation of an abstract unix domain socket succeeded.
528 25830
 * disable_aslr
529
 *        ASLR can be disabled.
530 25830
 * vtest_cmd <command>
531 23331
 *        vtest command <command> is available
532
 *
533
 * A feature name can be prefixed with an exclamation mark (!) to skip a
534
 * test if the feature is present.
535
 *
536
 * Be careful with ignore_unknown_macro, because it may cause a test with a
537
 * misspelled macro to fail silently. You should only need it if you must
538
 * run a test with strings of the form "${...}".
539
 */
540
541
#if ENABLE_COVERAGE
542
static const unsigned coverage = 1;
543
#else
544
static const unsigned coverage = 0;
545 2499
#endif
546
547 4514
#if ENABLE_ASAN
548 2499
static const unsigned asan = 1;
549 2499
#else
550 2499
static const unsigned asan = 0;
551
#endif
552 2499
553 0
#if ENABLE_MSAN
554 0
static const unsigned msan = 1;
555 0
#else
556
static const unsigned msan = 0;
557 2499
#endif
558 2499
559 2499
#if ENABLE_TSAN
560 2499
static const unsigned tsan = 1;
561 2499
#else
562 2499
static const unsigned tsan = 0;
563 2499
#endif
564 2499
565 2499
#if ENABLE_UBSAN
566 2499
static const unsigned ubsan = 1;
567 2499
#else
568 2499
static const unsigned ubsan = 0;
569 2499
#endif
570
571 2499
#if ENABLE_SANITIZER
572 0
static const unsigned sanitizer = 1;
573 0
#else
574 0
static const unsigned sanitizer = 0;
575 0
#endif
576 0
577 0
#if ENABLE_WORKSPACE_EMULATOR
578
static const unsigned workspace_emulator = 1;
579 0
#else
580 2499
static const unsigned workspace_emulator = 0;
581 0
#endif
582 0
583 0
#if WITH_PERSISTENT_STORAGE
584 0
static const unsigned with_persistent_storage = 1;
585 0
#else
586 0
static const unsigned with_persistent_storage = 0;
587
#endif
588 0
589 2499
void v_matchproto_(cmd_f)
590 987
cmd_feature(CMD_ARGS)
591 987
{
592 987
        const char *feat;
593 987
        int r, good, skip, neg;
594 0
595 987
        (void)priv;
596 987
597 252
        if (av == NULL)
598 2499
                return;
599 336
600 336
#define FEATURE(nm, tst)                                \
601 1512
        do {                                            \
602 189
                if (!strcmp(feat, nm)) {                \
603 189
                        good = 1;                       \
604 189
                        if (tst) {                      \
605 189
                                skip = neg;             \
606 0
                        } else {                        \
607 189
                                skip = !neg;            \
608 84
                        }                               \
609 189
                }                                       \
610 2499
        } while (0)
611 0
612
        skip = 0;
613 2499
614 2015
        for (av++; *av != NULL; av++) {
615
                good = 0;
616 484
                neg = 0;
617 484
                feat = *av;
618 0
619 0
                if (feat[0] == '!') {
620
                        neg = 1;
621 968
                        feat++;
622 484
                }
623 484
624
                FEATURE("ipv4", ipvx_works("127.0.0.1"));
625 25830
                FEATURE("ipv6", ipvx_works("[::1]"));
626
                FEATURE("64bit", sizeof(void*) == 8);
627
                FEATURE("disable_aslr", addr_no_randomize_works());
628
                FEATURE("dns", dns_works());
629
                FEATURE("topbuild", iflg);
630
                FEATURE("root", !geteuid());
631
                FEATURE("user_varnish", getpwnam("varnish") != NULL);   // XXX remove?
632
                FEATURE("user_vinyl", getpwnam("vinyl") != NULL);
633
                FEATURE("user_vcache", getpwnam("vcache") != NULL);
634
                FEATURE("group_varnish", getgrnam("varnish") != NULL); // XXX remove?
635
                FEATURE("group_vinyl", getgrnam("vinyl") != NULL);
636
                FEATURE("persistent_storage", with_persistent_storage);
637
                FEATURE("coverage", coverage);
638
                FEATURE("asan", asan);
639
                FEATURE("msan", msan);
640
                FEATURE("tsan", tsan);
641
                FEATURE("ubsan", ubsan);
642
                FEATURE("sanitizer", sanitizer);
643
                FEATURE("workspace_emulator", workspace_emulator);
644
                FEATURE("abstract_uds", abstract_uds_works());
645
646
                if (!strcmp(feat, "user")) {
647
                        av++;
648
                        if (*av == NULL)
649
                                vtc_fatal(vl, "Missing username");
650
                        good = 1;
651
                        if (getpwnam(*av) != NULL)
652
                                skip = neg;
653
                        else
654
                                skip = !neg;
655
                } else if (!strcmp(feat, "group")) {
656
                        av++;
657
                        if (*av == NULL)
658
                                vtc_fatal(vl, "Missing groupname");
659
                        good = 1;
660
                        if (getgrnam(*av) != NULL)
661
                                skip = neg;
662
                        else
663
                                skip = !neg;
664
                } else if (!strcmp(feat, "cmd")) {
665
                        good = 1;
666
                        skip = neg;
667
                        av++;
668
                        if (*av == NULL)
669
                                vtc_fatal(vl, "Missing the command-line");
670
                        r = system(*av);
671
                        if (WEXITSTATUS(r) != 0)
672
                                skip = !neg;
673
                } else if (!strcmp(feat, "ignore_unknown_macro")) {
674
                        ign_unknown_macro = 1;
675
                        good = 1;
676
                } else if (!strcmp(feat, "vtest_cmd")) {
677
                        good = 1;
678
                        skip = !neg;
679
                        av++;
680
                        if (*av == NULL)
681
                                vtc_fatal(vl, "vtest_cmd needs the command name");
682
                        if (find_cmd(*av) != NULL)
683
                                skip = neg;
684
                }
685
                if (!good)
686
                        vtc_fatal(vl, "FAIL test, unknown feature: %s", feat);
687
688
                if (!skip)
689
                        continue;
690
691
                vtc_stop = 2;
692
                if (neg)
693
                        vtc_log(vl, 1,
694
                            "SKIPPING test, conflicting feature: %s", feat);
695
                else
696
                        vtc_log(vl, 1,
697
                            "SKIPPING test, lacking feature: %s", feat);
698
                return;
699
        }
700
}