varnish-cache/bin/varnishtest/vtc_logexp.c
1
/*-
2
 * Copyright (c) 2008-2015 Varnish Software AS
3
 * All rights reserved.
4
 *
5
 * Author: Martin Blix Grydeland <martin@varnish-software.com>
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 AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
20
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26
 * SUCH DAMAGE.
27
 */
28
29
#ifdef VTEST_WITH_VTC_LOGEXPECT
30
31
/* SECTION: logexpect logexpect
32
 *
33
 * Reads the VSL and looks for records matching a given specification. It will
34
 * process records trying to match the first pattern, and when done, will
35
 * continue processing, trying to match the following pattern. If a pattern
36
 * isn't matched, the test will fail.
37
 *
38
 * logexpect threads are declared this way::
39
 *
40
 *         logexpect lNAME -v <id> [-g <grouping>] [-d 0|1] [-q query] \
41
 *                 [vsl arguments] {
42
 *                         expect <skip> <vxid> <tag> <regex>
43
 *                         expect <skip> <vxid> <tag> <regex>
44
 *                         ...
45
 *                 } [-start|-wait]
46
 *
47
 * And once declared, you can start them, or wait on them::
48
 *
49
 *         logexpect lNAME <-start|-wait>
50
 *
51
 * With:
52
 *
53
 * lNAME
54
 *         Name the logexpect thread, it must start with 'l'.
55
 *
56
 * \-v id
57
 *         Specify the varnish instance to use (most of the time, id=v1).
58
 *
59
 * \-g <session|request|vxid|raw
60
 *         Decide how records are grouped, see -g in ``man varnishlog`` for more
61
 *         information.
62
 *
63
 * \-d <0|1>
64
 *         Start processing log records at the head of the log instead of the
65
 *         tail.
66
 *
67
 * \-q query
68
 *         Filter records using a query expression, see ``man vsl-query`` for
69
 *         more information.
70
 * \-m
71
 *         Also emit log records for misses (only for debugging)
72
 *
73
 * \-start
74
 *         Start the logexpect thread in the background.
75
 *
76
 * \-wait
77
 *         Wait for the logexpect thread to finish
78
 *
79
 * VSL arguments (similar to the varnishlog options):
80
 *
81
 * \-b|-c
82
 *         Process only backend/client records.
83
 *
84
 * \-C
85
 *         Use caseless regex
86
 *
87
 * \-i <taglist>
88
 *         Include tags
89
 *
90
 * \-I <[taglist:]regex>
91
 *         Include by regex
92
 *
93
 * \-T <seconds>
94
 *         Transaction end timeout
95
 *
96
 * And the arguments of the specifications lines are:
97
 *
98
 * skip: [uint|*]
99
 *         Max number of record to skip
100
 *
101
 * vxid: [uint|*|=]
102
 *         vxid to match
103
 *
104
 * tag:  [tagname|*|=]
105
 *         Tag to match against
106
 *
107
 * regex:
108
 *         regular expression to match against (optional)
109
 *
110
 * For skip, vxid and tag, '*' matches anything, '=' expects the value of the
111
 * previous matched record.
112
 */
113
114
#include "config.h"
115
116
#include <stdlib.h>
117
#include <stdio.h>
118
#include <string.h>
119
#include <stdint.h>
120
121
#include "vapi/vsm.h"
122
#include "vapi/vsl.h"
123
124
#include "vtc.h"
125
126
#include "vtim.h"
127
#include "vre.h"
128
129
#define LE_ANY  (-1)
130
#define LE_LAST (-2)
131
132
struct logexp_test {
133
        unsigned                        magic;
134
#define LOGEXP_TEST_MAGIC               0x6F62B350
135
        VTAILQ_ENTRY(logexp_test)       list;
136
137
        struct vsb                      *str;
138
        int                             vxid;
139
        int                             tag;
140
        vre_t                           *vre;
141
        int                             skip_max;
142
};
143
144
struct logexp {
145
        unsigned                        magic;
146
#define LOGEXP_MAGIC                    0xE81D9F1B
147
        VTAILQ_ENTRY(logexp)            list;
148
149
        char                            *name;
150
        char                            *vname;
151
        struct vtclog                   *vl;
152
        char                            run;
153
        VTAILQ_HEAD(,logexp_test)       tests;
154
155
        struct logexp_test              *test;
156
        int                             skip_cnt;
157
        int                             vxid_last;
158
        int                             tag_last;
159
160
        int                             m_arg;
161
        int                             d_arg;
162
        enum VSL_grouping_e             g_arg;
163
        char                            *query;
164
165
        struct vsm                      *vsm;
166
        struct VSL_data                 *vsl;
167
        struct VSLQ                     *vslq;
168
        pthread_t                       tp;
169
};
170
171
static VTAILQ_HEAD(, logexp)            logexps =
172
        VTAILQ_HEAD_INITIALIZER(logexps);
173
174
static void
175 4720
logexp_delete_tests(struct logexp *le)
176
{
177
        struct logexp_test *test;
178
179 4720
        CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC);
180 19712
        while (!VTAILQ_EMPTY(&le->tests)) {
181 14992
                test = VTAILQ_FIRST(&le->tests);
182 14992
                CHECK_OBJ_NOTNULL(test, LOGEXP_TEST_MAGIC);
183 14992
                VTAILQ_REMOVE(&le->tests, test, list);
184 14992
                VSB_destroy(&test->str);
185 14992
                if (test->vre)
186 13216
                        VRE_free(&test->vre);
187 14992
                FREE_OBJ(test);
188
        }
189 4720
}
190
191
static void
192 1936
logexp_delete(struct logexp *le)
193
{
194 1936
        CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC);
195 1936
        AZ(le->run);
196 1936
        AN(le->vsl);
197 1936
        VSL_Delete(le->vsl);
198 1936
        AZ(le->vslq);
199 1936
        logexp_delete_tests(le);
200 1936
        free(le->name);
201 1936
        free(le->vname);
202 1936
        free(le->query);
203 1936
        VSM_Destroy(&le->vsm);
204 1936
        vtc_logclose(le->vl);
205 1936
        FREE_OBJ(le);
206 1936
}
207
208
static struct logexp *
209 1936
logexp_new(const char *name, const char *varg)
210
{
211
        struct logexp *le;
212
        struct vsb *n_arg;
213
214 1936
        ALLOC_OBJ(le, LOGEXP_MAGIC);
215 1936
        AN(le);
216 1936
        REPLACE(le->name, name);
217 1936
        le->vl = vtc_logopen(name);
218 1936
        VTAILQ_INIT(&le->tests);
219
220 1936
        le->d_arg = 0;
221 1936
        le->g_arg = VSL_g_vxid;
222 1936
        le->vsm = VSM_New();
223 1936
        le->vsl = VSL_New();
224 1936
        AN(le->vsm);
225 1936
        AN(le->vsl);
226
227 1936
        VTAILQ_INSERT_TAIL(&logexps, le, list);
228
229 1936
        REPLACE(le->vname, varg);
230
231 1936
        n_arg = macro_expandf(le->vl, "${tmpdir}/%s", varg);
232 1936
        if (n_arg == NULL)
233 0
                vtc_fatal(le->vl, "-v argument problems");
234 1936
        if (VSM_Arg(le->vsm, 'n', VSB_data(n_arg)) <= 0)
235 0
                vtc_fatal(le->vl, "-v argument error: %s",
236 0
                    VSM_Error(le->vsm));
237 1936
        VSB_destroy(&n_arg);
238 1936
        if (VSM_Attach(le->vsm, -1))
239 0
                vtc_fatal(le->vl, "VSM_Attach: %s", VSM_Error(le->vsm));
240 1936
        return (le);
241
}
242
243
static void
244 17808
logexp_next(struct logexp *le)
245
{
246 17808
        CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC);
247
248 17808
        if (le->test) {
249 14992
                CHECK_OBJ_NOTNULL(le->test, LOGEXP_TEST_MAGIC);
250 14992
                le->test = VTAILQ_NEXT(le->test, list);
251 14992
        } else
252 2816
                le->test = VTAILQ_FIRST(&le->tests);
253
254 17808
        CHECK_OBJ_ORNULL(le->test, LOGEXP_TEST_MAGIC);
255 17808
        if (le->test)
256 14992
                vtc_log(le->vl, 3, "expecting| %s", VSB_data(le->test->str));
257 17808
}
258
259
static int v_matchproto_(VSLQ_dispatch_f)
260 69941
logexp_dispatch(struct VSL_data *vsl, struct VSL_transaction * const pt[],
261
    void *priv)
262
{
263
        struct logexp *le;
264
        struct VSL_transaction *t;
265
        int i;
266
        int ok, skip;
267
        int vxid, tag, type, len;
268
        const char *legend, *data;
269
270 69941
        CAST_OBJ_NOTNULL(le, priv, LOGEXP_MAGIC);
271
272 137950
        for (i = 0; (t = pt[i]) != NULL; i++) {
273 291819
                while (1 == VSL_Next(t->c)) {
274 223806
                        if (!VSL_Match(vsl, t->c))
275 272
                                continue;
276
277 223468
                        CHECK_OBJ_NOTNULL(le->test, LOGEXP_TEST_MAGIC);
278 223485
                        AN(t->c->rec.ptr);
279 223537
                        vxid = VSL_ID(t->c->rec.ptr);
280 223537
                        tag = VSL_TAG(t->c->rec.ptr);
281 223537
                        data = VSL_CDATA(t->c->rec.ptr);
282 223537
                        len = VSL_LEN(t->c->rec.ptr) - 1;
283
284 223537
                        if (tag == SLT__Batch)
285 0
                                continue;
286
287 223529
                        ok = 1;
288 223529
                        if (le->test->vxid == LE_LAST) {
289 53259
                                if (le->vxid_last != vxid)
290 2128
                                        ok = 0;
291 223519
                        } else if (le->test->vxid >= 0) {
292 89966
                                if (le->test->vxid != vxid)
293 64239
                                        ok = 0;
294 89967
                        }
295 223529
                        if (le->test->tag == LE_LAST) {
296 0
                                if (le->tag_last != tag)
297 0
                                        ok = 0;
298 223515
                        } else if (le->test->tag >= 0) {
299 223515
                                if (le->test->tag != tag)
300 204207
                                        ok = 0;
301 223520
                        }
302 240930
                        if (le->test->vre &&
303 185717
                            le->test->tag >= 0 &&
304 185726
                            le->test->tag == tag &&
305 34840
                            VRE_ERROR_NOMATCH == VRE_exec(le->test->vre, data,
306 17420
                                len, 0, 0, NULL, 0, NULL))
307 4108
                                ok = 0;
308
309 223537
                        skip = 0;
310 223537
                        if (!ok && (le->test->skip_max == LE_ANY ||
311 80
                                le->test->skip_max > le->skip_cnt))
312 208527
                                skip = 1;
313
314 223519
                        if (ok)
315 14992
                                legend = "match";
316 208531
                        else if (skip && le->m_arg)
317 0
                                legend = "miss";
318 208528
                        else if (skip)
319 208537
                                legend = NULL;
320
                        else
321 0
                                legend = "err";
322 223521
                        type = VSL_CLIENT(t->c->rec.ptr) ? 'c' :
323 65267
                            VSL_BACKEND(t->c->rec.ptr) ? 'b' : '-';
324
325 223540
                        if (legend != NULL)
326 29984
                                vtc_log(le->vl, 4, "%-5s| %10u %-15s %c %.*s",
327 14992
                                    legend, vxid, VSL_tags[tag], type, len,
328 14992
                                    data);
329
330 223532
                        if (ok) {
331 14992
                                le->vxid_last = vxid;
332 14992
                                le->tag_last = tag;
333 14992
                                le->skip_cnt = 0;
334 14992
                                logexp_next(le);
335 14992
                                if (le->test == NULL)
336
                                        /* End of test script */
337 2784
                                        return (1);
338 220748
                        } else if (skip)
339 208541
                                le->skip_cnt++;
340
                        else {
341
                                /* Signal fail */
342 0
                                return (2);
343
                        }
344
                }
345 68007
        }
346
347 67158
        return (0);
348 69942
}
349
350
static void *
351 2816
logexp_thread(void *priv)
352
{
353
        struct logexp *le;
354
        int i;
355
356 2816
        CAST_OBJ_NOTNULL(le, priv, LOGEXP_MAGIC);
357 2816
        AN(le->run);
358 2816
        AN(le->vsm);
359 2816
        AN(le->vslq);
360
361 2816
        AZ(le->test);
362 2816
        vtc_log(le->vl, 4, "begin|");
363 2816
        if (le->query != NULL)
364 960
                vtc_log(le->vl, 4, "qry| %s", le->query);
365 2816
        logexp_next(le);
366 255259
        while (le->test) {
367 252445
                i = VSLQ_Dispatch(le->vslq, logexp_dispatch, le);
368 252445
                if (i == 2)
369 0
                        vtc_fatal(le->vl, "bad| expectation failed");
370 252423
                else if (i < 0)
371 0
                        vtc_fatal(le->vl, "bad| dispatch failed (%d)", i);
372 252420
                else if (i == 0 && le->test)
373 123659
                        VTIM_sleep(0.01);
374
        }
375 2816
        vtc_log(le->vl, 4, "end|");
376
377 2816
        return (NULL);
378
}
379
380
static void
381 2816
logexp_close(struct logexp *le)
382
{
383
384 2816
        CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC);
385 2816
        AN(le->vsm);
386 2816
        if (le->vslq)
387 2816
                VSLQ_Delete(&le->vslq);
388 2816
        AZ(le->vslq);
389 2816
}
390
391
static void
392 2816
logexp_start(struct logexp *le)
393
{
394
        struct VSL_cursor *c;
395
396 2816
        CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC);
397 2816
        AN(le->vsl);
398 2816
        AZ(le->vslq);
399
400 2816
        AN(le->vsl);
401 2816
        (void)VSM_Status(le->vsm);
402 5632
        c = VSL_CursorVSM(le->vsl, le->vsm,
403 2816
            (le->d_arg ? 0 : VSL_COPT_TAIL) | VSL_COPT_BATCH);
404 2816
        if (c == NULL)
405 0
                vtc_fatal(le->vl, "VSL_CursorVSM: %s", VSL_Error(le->vsl));
406 2816
        le->vslq = VSLQ_New(le->vsl, &c, le->g_arg, le->query);
407 2816
        if (le->vslq == NULL) {
408 0
                VSL_DeleteCursor(c);
409 0
                vtc_fatal(le->vl, "VSLQ_New: %s", VSL_Error(le->vsl));
410
        }
411 2816
        AZ(c);
412
413 2816
        le->test = NULL;
414 2816
        le->skip_cnt = 0;
415 2816
        le->vxid_last = le->tag_last = -1;
416 2816
        le->run = 1;
417 2816
        AZ(pthread_create(&le->tp, NULL, logexp_thread, le));
418 2816
}
419
420
static void
421 2816
logexp_wait(struct logexp *le)
422
{
423
        void *res;
424
425 2816
        CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC);
426 2816
        vtc_log(le->vl, 2, "Waiting for logexp");
427 2816
        AZ(pthread_join(le->tp, &res));
428 2816
        logexp_close(le);
429 2816
        if (res != NULL && !vtc_stop)
430 0
                vtc_fatal(le->vl, "logexp returned \"%p\"", (char *)res);
431 2816
        le->run = 0;
432 2816
}
433
434
static void
435 14992
cmd_logexp_expect(CMD_ARGS)
436
{
437
        struct logexp *le;
438
        int skip_max;
439
        int vxid;
440
        int tag;
441
        vre_t *vre;
442
        const char *err;
443
        int pos;
444
        struct logexp_test *test;
445
        char *end;
446
447 14992
        (void)cmd;
448 14992
        CAST_OBJ_NOTNULL(le, priv, LOGEXP_MAGIC);
449 14992
        if (av[1] == NULL || av[2] == NULL || av[3] == NULL)
450 0
                vtc_fatal(vl, "Syntax error");
451
452 14992
        if (av[4] != NULL && av[5] != NULL)
453 0
                vtc_fatal(vl, "Syntax error");
454
455 14992
        if (!strcmp(av[1], "*"))
456 7296
                skip_max = LE_ANY;
457
        else {
458 7696
                skip_max = (int)strtol(av[1], &end, 10);
459 7696
                if (*end != '\0' || skip_max < 0)
460 0
                        vtc_fatal(vl, "Not a positive integer: '%s'", av[1]);
461
        }
462 14992
        if (!strcmp(av[2], "*"))
463 2304
                vxid = LE_ANY;
464 12688
        else if (!strcmp(av[2], "="))
465 5952
                vxid = LE_LAST;
466
        else {
467 6736
                vxid = (int)strtol(av[2], &end, 10);
468 6736
                if (*end != '\0' || vxid < 0)
469 0
                        vtc_fatal(vl, "Not a positive integer: '%s'", av[2]);
470
        }
471 14992
        if (!strcmp(av[3], "*"))
472 0
                tag = LE_ANY;
473 14992
        else if (!strcmp(av[3], "="))
474 0
                tag = LE_LAST;
475
        else {
476 14992
                tag = VSL_Name2Tag(av[3], strlen(av[3]));
477 14992
                if (tag < 0)
478 0
                        vtc_fatal(vl, "Unknown tag name: '%s'", av[3]);
479
        }
480 14992
        vre = NULL;
481 14992
        if (av[4]) {
482 13216
                vre = VRE_compile(av[4], 0, &err, &pos);
483 13216
                if (vre == NULL)
484 0
                        vtc_fatal(vl, "Regex error (%s): '%s' pos %d",
485 0
                            err, av[4], pos);
486 13216
        }
487
488 14992
        ALLOC_OBJ(test, LOGEXP_TEST_MAGIC);
489 14992
        AN(test);
490 14992
        test->str = VSB_new_auto();
491 14992
        AN(test->str);
492 14992
        AZ(VSB_printf(test->str, "%s %s %s %s ", av[0], av[1], av[2], av[3]));
493 14992
        if (av[4])
494 13216
                VSB_quote(test->str, av[4], -1, 0);
495 14992
        AZ(VSB_finish(test->str));
496 14992
        test->skip_max = skip_max;
497 14992
        test->vxid = vxid;
498 14992
        test->tag = tag;
499 14992
        test->vre = vre;
500 14992
        VTAILQ_INSERT_TAIL(&le->tests, test, list);
501 14992
}
502
503
static const struct cmds logexp_cmds[] = {
504
        { "expect",             cmd_logexp_expect },
505
        { NULL,                 NULL },
506
};
507
508
static void
509 2784
logexp_spec(struct logexp *le, const char *spec)
510
{
511 2784
        CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC);
512
513 2784
        logexp_delete_tests(le);
514
515 2784
        parse_string(spec, logexp_cmds, le, le->vl);
516 2784
}
517
518
void
519 17728
cmd_logexpect(CMD_ARGS)
520
{
521
        struct logexp *le, *le2;
522
        int i;
523
524 17728
        (void)priv;
525 17728
        (void)cmd;
526
527 17728
        if (av == NULL) {
528
                /* Reset and free */
529 14736
                VTAILQ_FOREACH_SAFE(le, &logexps, list, le2) {
530 1936
                        CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC);
531 1936
                        VTAILQ_REMOVE(&logexps, le, list);
532 1936
                        if (le->run) {
533 16
                                (void)pthread_cancel(le->tp);
534 16
                                logexp_wait(le);
535 16
                        }
536 1936
                        logexp_delete(le);
537 1936
                }
538 12800
                return;
539
        }
540
541 4928
        AZ(strcmp(av[0], "logexpect"));
542 4928
        av++;
543
544 4928
        VTC_CHECK_NAME(vl, av[0], "Logexpect", 'l');
545 5984
        VTAILQ_FOREACH(le, &logexps, list) {
546 4048
                if (!strcmp(le->name, av[0]))
547 2992
                        break;
548 1056
        }
549 4928
        if (le == NULL) {
550 1936
                if (strcmp(av[1], "-v") || av[2] == NULL)
551 0
                        vtc_fatal(vl, "new logexp lacks -v");
552 1936
                le = logexp_new(av[0], av[2]);
553 1936
                av += 2;
554 1936
        }
555 4928
        av++;
556
557 17600
        for (; *av != NULL; av++) {
558 12672
                if (vtc_error)
559 0
                        break;
560 12672
                if (!strcmp(*av, "-wait")) {
561 2080
                        if (!le->run)
562 0
                                vtc_fatal(le->vl, "logexp not -started '%s'",
563 0
                                        *av);
564 2080
                        logexp_wait(le);
565 2080
                        continue;
566
                }
567
568
                /*
569
                 * We do an implict -wait if people muck about with a
570
                 * running logexp.
571
                 */
572 10592
                if (le->run)
573 0
                        logexp_wait(le);
574 10592
                AZ(le->run);
575
576 10592
                if (!strcmp(*av, "-v")) {
577 416
                        if (av[1] == NULL || strcmp(av[1], le->vname))
578 0
                                vtc_fatal(le->vl, "-v argument cannot change");
579 416
                        av++;
580 416
                        continue;
581
                }
582 10176
                if (!strcmp(*av, "-d")) {
583 1088
                        if (av[1] == NULL)
584 0
                                vtc_fatal(le->vl, "Missing -d argument");
585 1088
                        le->d_arg = atoi(av[1]);
586 1088
                        av++;
587 1088
                        continue;
588
                }
589 9088
                if (!strcmp(*av, "-g")) {
590 2464
                        if (av[1] == NULL)
591 0
                                vtc_fatal(le->vl, "Missing -g argument");
592 2464
                        i = VSLQ_Name2Grouping(av[1], strlen(av[1]));
593 2464
                        if (i < 0)
594 0
                                vtc_fatal(le->vl, "Unknown grouping '%s'",
595 0
                                    av[1]);
596 2464
                        le->g_arg = (enum VSL_grouping_e)i;
597 2464
                        av++;
598 2464
                        continue;
599
                }
600 6624
                if (!strcmp(*av, "-q")) {
601 960
                        if (av[1] == NULL)
602 0
                                vtc_fatal(le->vl, "Missing -q argument");
603 960
                        REPLACE(le->query, av[1]);
604 960
                        av++;
605 960
                        continue;
606
                }
607 5664
                if (!strcmp(*av, "-m")) {
608 0
                        le->m_arg = !le->m_arg;
609 0
                        continue;
610
                }
611 5664
                if (!strcmp(*av, "-start")) {
612 2096
                        logexp_start(le);
613 2096
                        continue;
614
                }
615 3568
                if (!strcmp(*av, "-run")) {
616 720
                        logexp_start(le);
617 720
                        logexp_wait(le);
618 720
                        continue;
619
                }
620 2848
                if (**av == '-') {
621 64
                        if (av[1] != NULL) {
622 64
                                if (VSL_Arg(le->vsl, av[0][1], av[1])) {
623 64
                                        av++;
624 64
                                        continue;
625
                                }
626 0
                                vtc_fatal(le->vl, "%s", VSL_Error(le->vsl));
627
                        }
628 0
                        vtc_fatal(le->vl, "Unknown logexp argument: %s", *av);
629
                }
630 2784
                logexp_spec(le, *av);
631 2784
        }
632 17728
}
633
634
#endif /* VTEST_WITH_VTC_LOGEXPECT */