[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