[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