[master] 63f7dfa Add logexpect to varnishtest.
Martin Blix Grydeland
martin at varnish-cache.org
Fri Jun 14 16:11:30 CEST 2013
commit 63f7dfa0b52d09f8da86c97078a2e496a4f59b83
Author: Martin Blix Grydeland <martin at varnish-software.com>
Date: Wed Jun 5 11:13:06 2013 +0200
Add logexpect to varnishtest.
This allows checking order and contents of VSL records in varnishtest.
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.com>
+ *
+ * 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);
+ }
+}
More information about the varnish-commit
mailing list