[PATCH 2/3] tests: scanner

Marek Ch mchqwerty at gmail.com
Fri Nov 29 05:52:32 PST 2013


Scanner is program that crawles through given files and
copies out every definition/declaration marked by WL_EXPORT_TEST.
It saves these definitions into test-runner/tests-private.[ch]
so that these definition can be tested. Using the scanner guarantee testing of
current code of functions (contrary to manual copying from source file)
---
 src/wayland-util.h                |   3 +
 tests/test-runner/Makefile.am     |  15 ++
 tests/test-runner/scanner-tests.c | 349 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 367 insertions(+)
 create mode 100644 tests/test-runner/scanner-tests.c

diff --git a/src/wayland-util.h b/src/wayland-util.h
index 68d91e2..5df0a54 100644
--- a/src/wayland-util.h
+++ b/src/wayland-util.h
@@ -44,6 +44,9 @@ extern "C" {
 #define WL_EXPORT
 #endif
 
+/* export function for tests */
+#define WL_EXPORT_TEST
+
 /* Deprecated attribute */
 #if defined(__GNUC__) && __GNUC__ >= 4
 #define WL_DEPRECATED __attribute__ ((deprecated))
diff --git a/tests/test-runner/Makefile.am b/tests/test-runner/Makefile.am
index b8f1e76..bd7c292 100644
--- a/tests/test-runner/Makefile.am
+++ b/tests/test-runner/Makefile.am
@@ -9,5 +9,20 @@ libtest_runner_la_SOURCES =	\
 libtest_helpers_la_SOURCES =	\
 	test-helpers.c
 
+scanner_input_files =	\
+	scanner-tests.c
+
+noinst_PROGRAMS = scanner-tests
+scanner_tests_SOURCES = scanner-tests.c
+
+$(BUILT_SOURCES) : scanner-tests $(scanner_input_files)
+	$(AM_V_GEN)./scanner-tests $(scanner_input_files)
+
+BUILT_SOURCES = \
+	tests-private.c	\
+	tests-private.h
+
 AM_CPPFLAGS = -I$(top_builddir)/src -I$(top_srcdir)/src
 AM_CFLAGS = $(GCC_CFLAGS)
+
+CLEANFILES = $(BUILT_SOURCES)
diff --git a/tests/test-runner/scanner-tests.c b/tests/test-runner/scanner-tests.c
new file mode 100644
index 0000000..427e47e
--- /dev/null
+++ b/tests/test-runner/scanner-tests.c
@@ -0,0 +1,349 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <err.h>
+#include <time.h>
+
+/* In order to make static functions testable too */
+#define WL_EXPORT_TEST
+
+WL_EXPORT_TEST
+const char *output = "tests-private.c";
+WL_EXPORT_TEST
+const char *output_header = "tests-private.h";
+
+WL_EXPORT_TEST
+static void
+extract_preprocessor(FILE *in, FILE *outc, FILE *outh)
+{
+	char name[8];
+	int chr, pchr = 0;
+
+	/* # was read, copy only directive */
+	fscanf(in, "%7s", name);
+
+	if (strcmp(name, "include") == 0) {
+		fprintf(outc, "#include ");
+		fprintf(outh, "#include ");
+		while ((chr = getc(in)) != EOF && chr !='\n') {
+			putc(chr, outc);
+			putc(chr, outh);
+		}
+	} else if (strcmp(name, "define") == 0) {
+		fprintf(outc, "\n#define ");
+		fprintf(outh, "\n#define ");
+		while ((chr = getc(in)) != EOF) {
+			if (chr == '\n' && pchr != '\\')
+				break;
+
+			putc(chr, outc);
+			putc(chr, outh);
+			pchr = chr;
+		}
+	}
+}
+
+WL_EXPORT_TEST
+static bool
+is_test_export(FILE *f)
+{
+	int chr;
+	unsigned i;
+	const char pattern[] = "WL_EXPORT_TEST";
+
+	/* W is already captured */
+	i = 1;
+
+	while ((chr = getc(f)) != EOF && i < strlen(pattern)) {
+		if (chr != pattern[i++])
+			return false;
+	}
+
+	/* did we go through the entire pattern? */
+	if (i != strlen(pattern))
+		return false;
+
+	return true;
+}
+
+WL_EXPORT_TEST
+static void *
+reallocate(void *mem, size_t *alloced)
+{
+	*alloced *= 2;
+	mem = realloc(mem, *alloced);
+	if (!mem)
+		abort();
+
+	return mem;
+}
+
+WL_EXPORT_TEST
+struct definition {
+	char *str;
+	enum {
+		NONE = 0,
+		VAR_DEF = 1,
+		VAR_DECL = 2,
+		FUNC = 3
+	} type;
+	int par; /* position of terminating parenthesis in prototype */
+};
+
+WL_EXPORT_TEST
+static struct definition
+extract_definition(FILE *in)
+{
+	int chr, pchr = 0;
+	bool got_body = false;
+	bool par = false; /* got parenthesis */
+	bool eq = false;
+	bool instring = false; /* set to true if inside of "" */
+	bool inquotes = false; /* '' */
+	bool incomment = false;
+	int braces = 0;
+	struct definition d = {NULL, NONE, 0};
+	size_t alloced = 500;
+	size_t i = 0;
+
+	d.str = malloc(alloced);
+	if (!d.str)
+		abort();
+
+	while ((chr = getc(in)) != EOF) {
+		switch (chr) {
+		/* handle comments */
+		case '*':
+			if (pchr == '/')
+				incomment = true;
+			break;
+		case '/':
+			if (pchr == '*')
+				incomment = false;
+			break;
+		/* handle strings and characters */
+		case '"':
+			if (pchr == '\\' || inquotes)
+				break;
+
+			instring = instring ? false : true;
+			break;
+		case '\'':
+			if ((pchr == '\\' && d.str[i - 2] != '\\') || instring)
+				break;
+
+			inquotes = inquotes ? false : true;
+			break;
+		/* count nesting */
+		case '{':
+			if (inquotes || instring || incomment)
+				break;
+
+			if (!got_body)
+				got_body = true;
+
+			braces++;
+			break;
+		case '}':
+			if (inquotes || instring || incomment)
+				break;
+
+			braces--;
+			break;
+		case '=':
+			/* distinguish declaration from definition */
+			if (!got_body)
+				eq = true;
+			break;
+		case ';':
+			if (got_body)
+				break;
+
+			/* variable definition/declaration */
+			/* here we can end */
+			d.str[i] = chr;
+			d.type = eq ? VAR_DEF : VAR_DECL;
+
+			if (i == alloced)
+				d.str = reallocate(d.str, &alloced);
+			d.str[i + 1] = 0;
+
+			return d;
+		case '(':
+			par = true;
+			break;
+		case ')':
+			/* save index of the prototype termination,
+			 * it will have a use later */
+			if (!braces)
+				d.par = i;
+			break;
+		}
+
+		d.str[i] = chr;
+		i++;
+
+		/* remove static if present */
+		if (i >= 6 && !got_body &&
+		    strncmp(d.str + i - 6, "static", 6) == 0) {
+			/* shift index so that static will be rewritten */
+			i -= 6;
+		}
+
+		if (braces == 0 && got_body)
+			break;
+
+		if (i == alloced)
+			d.str = reallocate(d.str, &alloced);
+
+		pchr = chr;
+	}
+
+	/* copy whatever is after last '}' */
+	if (got_body) {
+		while ((chr = getc(in)) != EOF && chr != '\n') {
+
+			/* struct decl or definition.
+			 * Can be in .h file */
+			if (chr == ';')
+				d.type = VAR_DECL;
+
+			d.str[i] = chr;
+
+			i++;
+			if (i == alloced) {
+				d.str = reallocate(d.str, &alloced);
+			}
+		}
+	}
+
+	d.str[i] = 0;
+	if (d.type == NONE)
+		d.type = FUNC;
+
+	return d;
+}
+
+static void
+write_header(FILE *out, const char *file)
+{
+	time_t t;
+	t = time(NULL);
+
+	fprintf(out,
+		"\n"
+		"/* -----------------------------------------------------------"
+			"--------\n"
+		" * Following code was copied from %s\n"
+		" * %s"
+		" * -----------------------------------------------------------"
+			"----- */\n",
+		file, ctime(&t));
+}
+
+int main(int argc, char *argv[])
+{
+	int i;
+	int chr;
+	bool newline = true;
+	FILE *fi, *fo, *fh;
+	struct definition d;
+	const char *def;
+
+	if (argc < 2)
+		errx(EXIT_FAILURE, "Usage: scanner-tests file1.c file2.c ...");
+
+	/* rewrite any old file */
+	fo = fopen(output, "wt");
+	if (!fo)
+		err(EXIT_FAILURE, "Failed creating file '%s'", output);
+	fh = fopen(output_header, "wt");
+	if (!fh)
+		err(EXIT_FAILURE, "Failed creating file '%s'",
+		    output_header);
+
+	/* include header into C file */
+	fprintf(fo, "#include \"%s\"\n", output_header);
+
+	for (i = 1; i < argc;  i++) {
+		fi = fopen(argv[i], "rt");
+		if (!fi)
+			err(EXIT_FAILURE, "Failed opening file '%s'", argv[i]);
+
+		write_header(fo, argv[i]);
+		write_header(fh, argv[i]);
+
+		while ((chr = getc(fi)) != EOF) {
+			if (chr == '#' && newline) {
+				extract_preprocessor(fi, fo, fh);
+				putc('\n', fo);
+				putc('\n', fh);
+				continue;
+			} else if (chr == 'W' && newline && is_test_export(fi)) {
+				d = extract_definition(fi);
+
+				/* skip whitespaces if there is any */
+				def = d.str;
+				while (def && isspace(*def))
+					def++;
+
+				switch(d.type) {
+				case VAR_DEF:
+					fprintf(fo, "%s\n", def);
+					break;
+				case VAR_DECL:
+					fprintf(fh, "%s\n", def);
+					break;
+				case FUNC:
+					fprintf(fo, "%s\n\n", def);
+					d.str[d.par + 1] = 0;
+					fprintf(fh, "%s;\n", def);
+					break;
+				case NONE:
+					fprintf(stderr, "not handled:\n%s\n",
+						d.str);
+				}
+
+				free(d.str);
+			}
+
+			/* ignore whitspaces on the begining of lines */
+			if (chr == '\n')
+				newline = true;
+
+			if (newline && !isspace(chr))
+				newline = false;
+		}
+
+		fclose(fi);
+	}
+
+	fclose(fh);
+	fclose(fo);
+
+	return 0;
+}
-- 
1.8.4.2



More information about the wayland-devel mailing list