
martin at varnish-software
Jun 13, 2013, 6:01 AM
Post #1 of 1
(75 views)
Permalink
|
|
[PATCH] Add logexpect to varnishtest.
|
|
This allows checking order and contents of VSL records in varnishtest. --- bin/varnishtest/Makefile.am | 3 +- bin/varnishtest/tests/README | 1 + bin/varnishtest/tests/l00000.vtc | 50 ++++ bin/varnishtest/vtc.c | 1 + bin/varnishtest/vtc.h | 1 + bin/varnishtest/vtc_logexp.c | 579 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 634 insertions(+), 1 deletion(-) create mode 100644 bin/varnishtest/tests/l00000.vtc create mode 100644 bin/varnishtest/vtc_logexp.c diff --git a/bin/varnishtest/Makefile.am b/bin/varnishtest/Makefile.am index b26e3cb..2ea62f5 100644 --- a/bin/varnishtest/Makefile.am +++ b/bin/varnishtest/Makefile.am @@ -30,7 +30,8 @@ varnishtest_SOURCES = \ vtc_log.c \ vtc_sema.c \ vtc_server.c \ - vtc_varnish.c + vtc_varnish.c \ + vtc_logexp.c varnishtest_LDADD = \ $(top_builddir)/lib/libvarnish/libvarnish.la \ diff --git a/bin/varnishtest/tests/README b/bin/varnishtest/tests/README index dc08fc5..c181fcc 100644 --- a/bin/varnishtest/tests/README +++ b/bin/varnishtest/tests/README @@ -18,6 +18,7 @@ Naming scheme id ~ [c] --> Complex functionality tests id ~ [e] --> ESI tests id ~ [g] --> GZIP tests + id ~ [l] --> VSL tests id ~ [m] --> VMOD tests id ~ [p] --> Persistent tests id ~ [r] --> Regression tests, same number as ticket diff --git a/bin/varnishtest/tests/l00000.vtc b/bin/varnishtest/tests/l00000.vtc new file mode 100644 index 0000000..90d9a7a --- /dev/null +++ b/bin/varnishtest/tests/l00000.vtc @@ -0,0 +1,50 @@ +varnishtest "test logexpect" + +server s1 { + rxreq + txresp +} -start + +varnish v1 -vcl+backend { +} -start + +logexpect l1 -v v1 -g session { + expect 0 1000 Begin sess + expect 0 = SessOpen + expect 0 = Link "req 1001" + expect 0 = SessClose + expect 0 = End + + expect 0 * Begin "req 1000" + expect 0 = ReqMethod GET + expect 0 = ReqURL / + expect 0 = ReqProtocol HTTP/1.1 + expect * = ReqHeader "Foo: bar" + expect * = Link bereq + expect * = ReqEnd + expect 0 = End + + expect 0 1002 Begin "bereq 1001" + expect * = End +} -start + +# Check with a query (this selects only the backend request) +logexpect l2 -v v1 -g vxid -q "bereq 1001" { + expect 0 1002 Begin + expect * = End +} -start + +client c1 { + txreq -hdr "Foo: bar" + rxresp + expect resp.status == 200 +} -run + +logexpect l1 -wait +logexpect l2 -wait + +# Check -d arg +logexpect l1 -d 1 { + expect 0 1000 Begin sess + expect * = SessClose +} -run diff --git a/bin/varnishtest/vtc.c b/bin/varnishtest/vtc.c index aeb4296..bea1298 100644 --- a/bin/varnishtest/vtc.c +++ b/bin/varnishtest/vtc.c @@ -535,6 +535,7 @@ static const struct cmds cmds[] = { { "sema", cmd_sema }, { "random", cmd_random }, { "feature", cmd_feature }, + { "logexpect", cmd_logexp }, { NULL, NULL } }; diff --git a/bin/varnishtest/vtc.h b/bin/varnishtest/vtc.h index 333a1df..85763df 100644 --- a/bin/varnishtest/vtc.h +++ b/bin/varnishtest/vtc.h @@ -63,6 +63,7 @@ cmd_f cmd_server; cmd_f cmd_client; cmd_f cmd_varnish; cmd_f cmd_sema; +cmd_f cmd_logexp; extern volatile sig_atomic_t vtc_error; /* Error, bail out */ extern int vtc_stop; /* Abandon current test, no error */ diff --git a/bin/varnishtest/vtc_logexp.c b/bin/varnishtest/vtc_logexp.c new file mode 100644 index 0000000..fc58cae --- /dev/null +++ b/bin/varnishtest/vtc_logexp.c @@ -0,0 +1,579 @@ +/*- + * Copyright (c) 2008-2013 Varnish Software AS + * All rights reserved. + * + * Author: Martin Blix Grydeland <martin [at] varnish-software> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Synopsis: + * -v <varnish-instance> + * -d <0|1> (head/tail mode) + * -g <grouping-mode> + * -q <query> + * + * logexpect lN -v <id> [-g <grouping>] [-d 0|1] [-q query] { + * expect <skip> <vxid> <tag> <regex> + * } + * + * skip: [uint|*] Max number of record to skip + * vxid: [uint|*|=] vxid to match + * tag: [tagname|*|=] Tag to match against + * regex: regular expression to match against (optional) + * *: Match anything + * =: Match value of last examined log record + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <errno.h> + +#include "vapi/vsm.h" +#include "vapi/vsl.h" +#include "vtim.h" +#include "vqueue.h" +#include "miniobj.h" +#include "vas.h" +#include "vre.h" + +#include "vtc.h" + +#define LE_ANY (-1) +#define LE_LAST (-2) + +struct logexp_test { + unsigned magic; +#define LOGEXP_TEST_MAGIC 0x6F62B350 + VTAILQ_ENTRY(logexp_test) list; + + struct vsb *str; + int vxid; + int tag; + vre_t *vre; + int skip_max; +}; + +struct logexp { + unsigned magic; +#define LOGEXP_MAGIC 0xE81D9F1B + VTAILQ_ENTRY(logexp) list; + + char *name; + struct vtclog *vl; + char run; + char *spec; + VTAILQ_HEAD(,logexp_test) tests; + + struct logexp_test *test; + int skip_cnt; + int vxid_last; + int tag_last; + + int d_arg; + int g_arg; + char *query; + + struct VSM_data *vsm; + struct VSL_data *vsl; + struct VSLQ *vslq; + pthread_t tp; +}; + +#define VSL_SLEEP_USEC (50*1000) + +static VTAILQ_HEAD(, logexp) logexps = + VTAILQ_HEAD_INITIALIZER(logexps); + +static void +logexp_delete_tests(struct logexp *le) +{ + struct logexp_test *test; + + CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC); + while ((test = VTAILQ_FIRST(&le->tests))) { + CHECK_OBJ_NOTNULL(test, LOGEXP_TEST_MAGIC); + VTAILQ_REMOVE(&le->tests, test, list); + VSB_delete(test->str); + if (test->vre) + VRE_free(&test->vre); + FREE_OBJ(test); + } +} + +static void +logexp_delete(struct logexp *le) +{ + CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC); + AZ(le->run); + AZ(le->vsl); + AZ(le->vslq); + logexp_delete_tests(le); + free(le->name); + free(le->query); + VSM_Delete(le->vsm); + FREE_OBJ(le); +} + +static struct logexp * +logexp_new(const char *name) +{ + struct logexp *le; + + AN(name); + ALLOC_OBJ(le, LOGEXP_MAGIC); + AN(le); + REPLACE(le->name, name); + le->vl = vtc_logopen(name); + VTAILQ_INIT(&le->tests); + + le->d_arg = 0; + le->g_arg = VSL_g_vxid; + le->vsm = VSM_New(); + AN(le->vsm); + + VTAILQ_INSERT_TAIL(&logexps, le, list); + return (le); +} + +static void +logexp_next(struct logexp *le) +{ + CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC); + + if (le->test) { + CHECK_OBJ_NOTNULL(le->test, LOGEXP_TEST_MAGIC); + le->test = VTAILQ_NEXT(le->test, list); + } else + le->test = VTAILQ_FIRST(&le->tests); + + CHECK_OBJ_ORNULL(le->test, LOGEXP_TEST_MAGIC); + if (le->test) + vtc_log(le->vl, 3, "tst| %s", VSB_data(le->test->str)); +} + +static int +logexp_dispatch(struct VSL_data *vsl, struct VSL_transaction *pt[], void *priv) +{ + struct logexp *le; + struct VSL_transaction *t; + int i; + int ok, skip; + int vxid, tag, type, len, lvl; + const char *legend, *data; + + (void)vsl; + CAST_OBJ_NOTNULL(le, priv, LOGEXP_MAGIC); + + for (i = 0; (t = pt[i]); i++) { + while (1 == VSL_Next(t->c)) { + CHECK_OBJ_NOTNULL(le->test, LOGEXP_TEST_MAGIC); + AN(t->c->rec.ptr); + vxid = VSL_ID(t->c->rec.ptr); + tag = VSL_TAG(t->c->rec.ptr); + data = VSL_CDATA(t->c->rec.ptr); + len = VSL_LEN(t->c->rec.ptr); + + if (tag == SLT__Batch) + continue; + + ok = 1; + if (le->test->vxid == LE_LAST) { + if (le->vxid_last != vxid) + ok = 0; + } else if (le->test->vxid >= 0) { + if (le->test->vxid != vxid) + ok = 0; + } + if (le->test->tag == LE_LAST) { + if (le->tag_last != tag) + ok = 0; + } else if (le->test->tag >= 0) { + if (le->test->tag != tag) + ok = 0; + } + if (le->test->vre && + VRE_ERROR_NOMATCH == VRE_exec(le->test->vre, data, + len, 0, 0, NULL, 0, NULL)) + ok = 0; + + skip = 0; + if (!ok && (le->test->skip_max == LE_ANY || + le->test->skip_max > le->skip_cnt)) + skip = 1; + + if (ok) { + lvl = 4; + legend = "ok"; + } else if (skip) { + lvl = 4; + legend = "skp"; + } else { + lvl = 0; + legend = "err"; + } + type = VSL_CLIENT(t->c->rec.ptr) ? 'c' : + VSL_BACKEND(t->c->rec.ptr) ? 'b' : '-'; + + vtc_log(le->vl, lvl, "%3s| %10u %-15s %c %.*s", + legend, vxid, VSL_tags[tag], type, len, data); + + if (ok) { + le->skip_cnt = 0; + logexp_next(le); + if (le->test == NULL) + /* End of test script */ + return (0); + } + if (skip) + le->skip_cnt++; + + le->vxid_last = vxid; + le->tag_last = tag; + } + } + + return (0); +} + +static void * +logexp_thread(void *priv) +{ + struct logexp *le; + int i; + + CAST_OBJ_NOTNULL(le, priv, LOGEXP_MAGIC); + AN(le->run); + AN(le->vsm); + AN(le->vslq); + + i = 0; + AZ(le->test); + logexp_next(le); + while (le->test) { + i = VSLQ_Dispatch(le->vslq, &logexp_dispatch, le); + if (i < 0) + vtc_log(le->vl, 0, "dispatch: %d", i); + if (i == 0 && le->test) + VTIM_sleep(0.01); + } + vtc_log(le->vl, 4, "end of test script"); + + return (NULL); +} + +static void +logexp_close(struct logexp *le) +{ + + CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC); + AN(le->vsm); + if (le->vslq) + VSLQ_Delete(&le->vslq); + AZ(le->vslq); + if (le->vsl) { + VSL_Delete(le->vsl); + le->vsl = NULL; + } + VSM_Close(le->vsm); +} + +static void +logexp_start(struct logexp *le) +{ + struct VSL_cursor *c; + + CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC); + AZ(le->vsl); + AZ(le->vslq); + + if (VSM_Open(le->vsm)) { + vtc_log(le->vl, 0, "VSM_Open: %s", VSM_Error(le->vsm)); + return; + } + le->vsl = VSL_New(); + AN(le->vsl); + c = VSL_CursorVSM(le->vsl, le->vsm, !le->d_arg); + if (c == NULL) { + vtc_log(le->vl, 0, "VSL_CursorVSM: %s", VSL_Error(le->vsl)); + logexp_close(le); + return; + } + le->vslq = VSLQ_New(le->vsl, &c, le->g_arg, le->query); + if (le->vslq == NULL) { + VSL_DeleteCursor(c); + vtc_log(le->vl, 0, "VSLQ_New: %s", VSL_Error(le->vsl)); + AZ(le->vslq); + logexp_close(le); + return; + } + AZ(c); + + le->test = NULL; + le->skip_cnt = 0; + le->vxid_last = le->tag_last = -1; + le->run = 1; + AZ(pthread_create(&le->tp, NULL, logexp_thread, le)); +} + +static void +logexp_wait(struct logexp *le) +{ + void *res; + + CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC); + vtc_log(le->vl, 2, "Waiting for logexp"); + AZ(pthread_join(le->tp, &res)); + logexp_close(le); + if (res != NULL && !vtc_stop) + vtc_log(le->vl, 0, "logexp returned \"%p\"", (char *)res); + le->run = 0; +} + +static void +cmd_logexp_expect(CMD_ARGS) +{ + struct logexp *le; + int skip_max; + int vxid; + int tag; + vre_t *vre; + const char *err; + int pos; + struct logexp_test *test; + char *end; + + (void)cmd; + CAST_OBJ_NOTNULL(le, priv, LOGEXP_MAGIC); + if (av[1] == NULL || av[2] == NULL || av[3] == NULL) { + vtc_log(vl, 0, "Syntax error"); + return; + } + + if (!strcmp(av[1], "*")) + skip_max = LE_ANY; + else { + skip_max = (int)strtol(av[1], &end, 10); + if (*end != '\0' || skip_max < 0) { + vtc_log(vl, 0, "Not a positive integer: '%s'", av[1]); + return; + } + } + if (!strcmp(av[2], "*")) + vxid = LE_ANY; + else if (!strcmp(av[2], "=")) + vxid = LE_LAST; + else { + vxid = (int)strtol(av[2], &end, 10); + if (*end != '\0' || vxid < 0) { + vtc_log(vl, 0, "Not a positive integer: '%s'", av[2]); + return; + } + } + if (!strcmp(av[3], "*")) + tag = LE_ANY; + else if (!strcmp(av[3], "=")) + tag = LE_LAST; + else { + tag = VSL_Name2Tag(av[3], strlen(av[3])); + if (tag < 0) { + vtc_log(vl, 0, "Unknown tag name: '%s'", av[3]); + return; + } + } + vre = NULL; + if (av[4]) { + vre = VRE_compile(av[4], 0, &err, &pos); + if (vre == NULL) { + vtc_log(vl, 0, "Regex error (%s): '%s' pos %d", + err, av[4], pos); + return; + } + } + + ALLOC_OBJ(test, LOGEXP_TEST_MAGIC); + AN(test); + test->str = VSB_new_auto(); + AN(test->str); + AZ(VSB_printf(test->str, "%s %s %s %s ", av[0], av[1], av[2], av[3])); + if (av[4]) + VSB_quote(test->str, av[4], -1, 0); + AZ(VSB_finish(test->str)); + test->skip_max = skip_max; + test->vxid = vxid; + test->tag = tag; + test->vre = vre; + VTAILQ_INSERT_TAIL(&le->tests, test, list); + vtc_log(vl, 4, "%s", VSB_data(test->str)); +} + +static const struct cmds logexp_cmds[] = { + { "expect", cmd_logexp_expect }, + { NULL, NULL }, +}; + +static void +logexp_spec(struct logexp *le, const char *spec) +{ + char *s; + + CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC); + + logexp_delete_tests(le); + + s = strdup(spec); + AN(s); + parse_string(s, logexp_cmds, le, le->vl); + free(s); +} + +void +cmd_logexp(CMD_ARGS) +{ + struct logexp *le, *le2; + const char tmpdir[] = "${tmpdir}"; + struct vsb *vsb, *vsb2; + + (void)priv; + (void)cmd; + (void)vl; + + if (av == NULL) { + /* Reset and free */ + VTAILQ_FOREACH_SAFE(le, &logexps, list, le2) { + CHECK_OBJ_NOTNULL(le, LOGEXP_MAGIC); + VTAILQ_REMOVE(&logexps, le, list); + if (le->run) { + (void)pthread_cancel(le->tp); + logexp_wait(le); + } + logexp_delete(le); + } + return; + } + + assert(!strcmp(av[0], "logexpect")); + av++; + + VTAILQ_FOREACH(le, &logexps, list) { + if (!strcmp(le->name, av[0])) + break; + } + if (le == NULL) + le = logexp_new(av[0]); + av++; + + for (; *av != NULL; av++) { + if (vtc_error) + break; + if (!strcmp(*av, "-wait")) { + if (!le->run) { + vtc_log(le->vl, 0, "logexp not -started '%s'", + *av); + return; + } + logexp_wait(le); + continue; + } + + /* + * We do an implict -wait if people muck about with a + * running logexp. + */ + if (le->run) + logexp_wait(le); + AZ(le->run); + + if (!strcmp(*av, "-v")) { + if (av[1] == NULL) { + vtc_log(le->vl, 0, "Missing -v argument"); + return; + } + vsb = VSB_new_auto(); + AZ(VSB_printf(vsb, "%s/%s", tmpdir, av[1])); + AZ(VSB_finish(vsb)); + vsb2 = macro_expand(le->vl, VSB_data(vsb)); + VSB_delete(vsb); + if (vsb2 == NULL) + return; + if (VSM_n_Arg(le->vsm, VSB_data(vsb2)) <= 0) { + vtc_log(le->vl, 0, "-v argument error: %s", + VSM_Error(le->vsm)); + return; + } + VSB_delete(vsb2); + av++; + continue; + } + if (!strcmp(*av, "-d")) { + if (av[1] == NULL) { + vtc_log(le->vl, 0, "Missing -d argument"); + return; + } + le->d_arg = atoi(av[1]); + av++; + continue; + } + if (!strcmp(*av, "-g")) { + if (av[1] == NULL) { + vtc_log(le->vl, 0, "Missing -g argument"); + return; + } + le->g_arg = VSLQ_Name2Grouping(av[1], strlen(av[1])); + if (le->g_arg < 0) { + vtc_log(le->vl, 0, "Unknown grouping '%s'", + av[1]); + return; + } + av++; + continue; + } + if (!strcmp(*av, "-q")) { + if (av[1] == NULL) { + vtc_log(le->vl, 0, "Missing -q argument"); + return; + } + REPLACE(le->query, av[1]); + av++; + continue; + } + if (!strcmp(*av, "-start")) { + logexp_start(le); + continue; + } + if (!strcmp(*av, "-run")) { + logexp_start(le); + logexp_wait(le); + continue; + } + if (**av == '-') { + vtc_log(le->vl, 0, "Unknown logexp argument: %s", *av); + return; + } + logexp_spec(le, *av); + } +} -- 1.7.10.4 _______________________________________________ varnish-dev mailing list varnish-dev [at] varnish-cache https://www.varnish-cache.org/lists/mailman/listinfo/varnish-dev
|