[PATCH evemu 1/3] py: add a tool to check the libevemu python binding

Benjamin Tissoires benjamin.tissoires at gmail.com
Mon Jan 13 15:30:30 PST 2014


The test check-API.py is launched at each make, and verifies if the
exported evemu API matches the internal libevemu python binding.
The check only matches function names, and not signature.

Without the python/evemu/base.py modification, the make output is:

$ make
/usr/bin/python check-API.py ../src/libevemu.ver
The following functions are missing in the python binding:
 * evemu_create_managed
 * evemu_get_devnode
make: *** [check_python_api] Error 1

Signed-off-by: Benjamin Tissoires <benjamin.tissoires at gmail.com>
---

Hi guys,

this is a follow up of Daniel's series regarding python 3.0.

I have a problem here (sorry I did not spend hours on this) regarding the
Makefile.am. I harcoded ../src/libevemu.ver, but $(srcdir) returned '.', so it
was of no use here.

Any comments welcome!

Cheers,
Benjamin

PS: sorry for having written the first stages of a compiler, but I did not found
any other elegant way to extract the info from libevemu.ver... :(

 python/Makefile.am   |   7 ++-
 python/check-API.py  | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++
 python/evemu/base.py |  12 ++++
 3 files changed, 184 insertions(+), 2 deletions(-)
 create mode 100644 python/check-API.py

diff --git a/python/Makefile.am b/python/Makefile.am
index 3ce3946..ddaf44b 100644
--- a/python/Makefile.am
+++ b/python/Makefile.am
@@ -32,6 +32,9 @@ evemu-test-runner: evemu-test-runner.in Makefile
 	  $< >$@
 	chmod +x $@
 
-BUILT_SOURCES = evemu-test-runner
-EXTRA_DIST =  evemu-test-runner.in $(wildcard evemu/test*)
+check_python_api: check-API.py Makefile ../src/libevemu.ver evemu/base.py
+	$(PYTHON) check-API.py ../src/libevemu.ver
+
+BUILT_SOURCES = evemu-test-runner check_python_api
+EXTRA_DIST =  evemu-test-runner.in $(wildcard evemu/test*) check-API.py
 CLEANFILES = $(BUILT_SOURCES)
diff --git a/python/check-API.py b/python/check-API.py
new file mode 100644
index 0000000..e983542
--- /dev/null
+++ b/python/check-API.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright 2014 Red Hat, Inc.
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+#  MA 02110-1301, USA.
+#
+
+#
+# Check that src/libevemu.ver and the API defined in python/evemu/base.py match
+#
+
+from __future__ import print_function
+import evemu.base
+import re
+
+class LexicalError(Exception):
+	pass
+
+class SemanticError(Exception):
+	pass
+
+lex_regexps = (
+	("state", re.compile(r'\s*(global|local)\s*:(.*)')),
+	("token", re.compile(r'\s*([a-zA-Z_\*][a-zA-Z_0-9\.]*)(.*)')),
+	("delim", re.compile(r'\s*([\{\}:;])(.*)')),
+)
+
+def next_lexem(input):
+	if input == "":
+		return None, None, None
+	for _type, regexp in lex_regexps:
+		m =  regexp.match(input)
+		if m:
+			return m.group(1), _type, m.group(2)
+	raise LexicalError("unable to parse input string:'{0}'".format(input))
+
+def lexical_analysis(input):
+	"""
+	Lexical analysis:
+	split the input file into tokens.
+	For example:
+	EVEMU_2.0 {
+		global:
+			evemu_create;
+			evemu_create_event;
+	};
+	is transformed into:
+	[('EVEMU_2.0', 'token'), ('{', 'delim'), ('global', 'state'), ('evemu_create', 'token'),
+	 (';', 'delim'), ('evemu_create_event', 'token'), (';', 'delim'),
+	 ('}', 'delim'), (';', 'delim')]
+	"""
+	lex = []
+	lexem, _type, input = next_lexem(input)
+	while input != None:
+		lex.append((lexem, _type))
+		lexem, _type, input = next_lexem(input)
+	return lex
+
+def semantic_analysis(lex):
+	"""
+	Semantical analysis:
+	Consume the tokens in lex and build an abstract representation of the file.
+	The previous example gives:
+	{'EVEMU_2.0': {'global': ['evemu_create', 'evemu_create_event']}}
+	"""
+	sem = {}
+	current_node = None
+	current_dict = None
+	current_state_list = None
+	for l, _type in lex:
+		if _type == "token":
+			current_node = l
+		elif _type == "delim":
+			if l == "{":
+				current_dict = {}
+				sem[current_node] = current_dict
+				current_node = None
+			elif l == "}":
+				current_dict = None
+				current_state_list = None
+			elif l == ";":
+				if current_state_list != None:
+					current_state_list.append(current_node)
+					current_node = None
+		elif _type == "state":
+			current_state_list = []
+			current_dict[l] = current_state_list
+
+	return sem
+
+def parse_lib_ver(path):
+	with open(path, "r") as lib_ver:
+		feed = "".join(lib_ver.readlines()).replace("\n", "")
+	lex = lexical_analysis(feed)
+	sem = semantic_analysis(lex)
+
+	# now that the semantical analysis is done, get only global scope functions
+	exported_functions = []
+	for lib in sem.values():
+		if "global" in lib:
+			exported_functions.extend(lib['global'])
+	return exported_functions
+
+def compare_functions(exported, internal):
+	"""
+	Compare two lists, returns:
+	error, missing_exported, missing_internal
+
+	with
+	- error: True if the two lists are not identical
+	- missing_exported: items in internal not in exported
+	- missing_internal: items in exported not in internal
+	"""
+	missings_internal = []
+	missings_exported = []
+
+	for f in exported:
+		if f not in internal:
+			missings_internal.append(f)
+	for f in internal:
+		if f not in exported:
+			missings_exported.append(f)
+
+	error = len(missings_internal) > 0 or len(missings_exported) > 0
+
+	return error, missings_exported, missings_internal
+
+
+def main():
+	import sys
+	path = "src/libevemu.ver"
+	if len(sys.argv) > 1:
+		path = sys.argv[1]
+	exp_fun = parse_lib_ver(path)
+	int_fun = list(evemu.base.LibEvemu._api_prototypes.keys())
+	err, m_e, m_i = compare_functions(exp_fun, int_fun)
+
+	if not err:
+		"the two provided lists are identical, good job"
+		sys.exit(0)
+
+	if len(m_e):
+		print("The following functions are not available anymore:")
+		for f in m_e:
+			print(" *", f)
+	if len(m_i):
+		print("The following functions are missing in the python binding:")
+		for f in m_i:
+			print(" *", f)
+	sys.exit(1)
+
+if __name__ == "__main__":
+	main()
diff --git a/python/evemu/base.py b/python/evemu/base.py
index b366e2f..1d903b2 100644
--- a/python/evemu/base.py
+++ b/python/evemu/base.py
@@ -186,6 +186,12 @@ class LibEvemu(LibraryWrapper):
             "argtypes": (c_void_p,),
             "restype": c_uint,
             },
+        #const char *evemu_get_devnode(struct evemu_device *dev);
+        "evemu_get_devnode": {
+            "argtypes": (c_void_p,),
+            "restype": c_char_p,
+            "errcheck": expect_not_none
+            },
         #const char *evemu_get_name(const struct evemu_device *dev);
         "evemu_get_name": {
             "argtypes": (c_void_p,),
@@ -394,6 +400,12 @@ class LibEvemu(LibraryWrapper):
             "restype": c_int,
             "errcheck": expect_eq_zero
             },
+        #int evemu_create_managed(struct evemu_device *dev);
+        "evemu_create_managed": {
+            "argtypes": (c_void_p,),
+            "restype": c_int,
+            "errcheck": expect_eq_zero
+            },
         #void evemu_destroy(struct evemu_device *dev);
         "evemu_destroy": {
             "argtypes": (c_void_p,),
-- 
1.8.4.2



More information about the Input-tools mailing list