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

Peter Hutterer peter.hutterer at who-t.net
Mon Jan 13 21:15:12 PST 2014


On Mon, Jan 13, 2014 at 06:30:30PM -0500, Benjamin Tissoires wrote:
> 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.

$(srcdir) always returns '.' unless you've got a build-dir specified (like
make distcheck does, that's good way to test changes like this btw).
automake should convert relative paths correctly, if in doubt use
$(top_srcdir)/src/libevemu.ver

> 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... :(

generate the header, libevemu.ver and python bits from the same source
instead of going the other way round. Have a look at
https://github.com/whot/evemu/tree/wip/xml-header
what do you think of that? nees a bit more polishing, but otherwise works.

errcheck needs to be added, and any special ABI generation would need to be
handled too though that should be simple enough to add.

> 
>  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)

did you test this with multi-ABI defines as well? do we need extensions to the parser then.

> +	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

    return frozenset(exported).symmetric_difference(internal)

gives you the elements ein either but not both. alternatively, you can do

    missing_internal = frozenset(exported).difference(internal)
    missing_exported = frozenset(internal).difference(exported)

and just return the two lists and use len() as an error indicator, err is
superfluous here (that's C code ;)

> +
> +
> +def main():
> +	import sys
> +	path = "src/libevemu.ver"
> +	if len(sys.argv) > 1:
> +		path = sys.argv[1]

given that this will be invoked by make, I'd just make the arguments
mandatory so we don't need hardcoded paths here.

> +	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"

no error message on success please.

> +		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,),

this should be a separate patch, Reviewed-by: Peter Hutterer <peter.hutterer at who-t.net>

Cheers,
   Peter



More information about the Input-tools mailing list