dbus-deviation-0.6.0/ 0000775 0001750 0001750 00000000000 13035747236 015242 5 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/.gitignore 0000664 0001750 0001750 00000000124 13026466535 017227 0 ustar philip philip 0000000 0000000 /*.egg-info/
/*.egg
/.eggs/
/build/
/dist/
/htmlcov/
/.coverage
/.be/id-cache
*.pyc
dbus-deviation-0.6.0/pre-push.hook 0000664 0001750 0001750 00000002235 13026466535 017671 0 ustar philip philip 0000000 0000000 #!/bin/sh
# A hook script to update the D-Bus API signature database every time a release
# tag is pushed. See the git-hooks documentation for the parameters it’s called
# with. See $(top_srcdir)/dbus-deviation.mk for more information about how this
# project uses dbus-deviation to check D-Bus API stability.
#
# If the API signature database is updated, a git note will be added to the new
# tag for each D-Bus API in the project, and the notes will be pushed remotely.
remote="$1"
url="$2"
# Get the configuration from the Makefile.
# This is pretty terrible.
eval `make dbus-deviation-mk-config`
if [ "$GIT" == "" ]; then
GIT=git
fi
if [ "$GIT_DIR" == "" ]; then
GIT_DIR=`git rev-parse --git-dir`
fi
if [ "$GIT_WORK_TREE" == "" ]; then
GIT_WORK_TREE=`pwd`
fi
IFS=' '
while read local_ref local_sha remote_ref remote_sha; do
if ! $GIT rev-parse --verify $local_ref^{tag} &> /dev/null; then
continue
fi
# Ignore failures from this.
dbus-interface-vcs-helper \
--git "$GIT" \
--git-dir "$GIT_DIR" \
--git-work-tree "$GIT_WORK_TREE" \
--git-refs "$dbus_api_git_refs" \
--git-remote "$remote" \
dist --ignore-existing $dbus_api_xml_files
done
exit 0
dbus-deviation-0.6.0/setup.cfg 0000664 0001750 0001750 00000000164 13035747236 017064 0 ustar philip philip 0000000 0000000 [pep8]
exclude = *.egg,docs/conf.py,version.py,setup.py
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
dbus-deviation-0.6.0/dbus-deviation.doap 0000664 0001750 0001750 00000002143 13026466535 021024 0 ustar philip philip 0000000 0000000
dbus-deviation
dbus-deviation is a project for parsing D-Bus introspection XML and processing it in various ways
dbus-deviation is a project for parsing D-Bus introspection XML and processing it in various ways. Its main tool is dbus-interface-diff, which calculates the difference between two D-Bus APIs for the purpose of checking for API breaks. This functionality is also available as a Python module, dbusdeviation.
Philip Withnall
dbus-deviation-0.6.0/website/ 0000775 0001750 0001750 00000000000 13035747236 016704 5 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/website/errors.html 0000664 0001750 0001750 00000046143 13026466535 021116 0 ustar philip philip 0000000 0000000
dbus-deviation: Error codes
dbus-deviation: Error codes
A list of all the possible error codes produced by dbus-deviation, along
with potential causes, what problems result, and how to avoid them.
Parser errors
Error code
Compatibility
Description
duplicate-interface
error
Two D-Bus interfaces with the same name were defined
in a single API file. One of the interfaces may be
misnamed.
duplicate-method
error
Two methods with the same name were defined in a
single D-Bus interface. One of the methods may be
misnamed.
duplicate-property
error
Two properties with the same name were defined in a
single D-Bus interface. One of the properties may be
misnamed.
duplicate-signal
error
Two signals with the same name were defined in a
single D-Bus interface. One of the signals may be
misnamed.
missing-attribute
error
An XML node in the API file was missing a required
attribute. Typically, a ‘name’ attribute is
required — check the
Introspection Data Format specification .
unknown-node
error
An XML node in the API file was not recognized.
Check the
Introspection Data Format specification .
Comparator errors
Error code
Compatibility
Description
argument-added
backwards-incompatible
A new argument was added to a method or signal. This
is backwards incompatible as all existing call sites
and signal handlers need to be changed to handle
it.
argument-direction-changed-*-*
backwards-incompatible
An argument’s direction was changed from ‘in’ to
‘out’ or vice-versa. This is backwards incompatible
as all existing call sites and signal handlers need
to be changed to write the argument when they would
have previously read it, or vice-versa.
argument-name-changed
info
An argument changed name. This is not an API break,
as arguments are looked up by index. Note that it
is an API break if two arguments have swapped places
though. You should check that the name change is not
accompanied by a change in the semantics of the
argument.
argument-removed
backwards-incompatible
An argument was removed from a method or signal.
This is backwards incompatible as all existing call
sites and signal handlers need to be changed to stop
using it.
argument-type-changed
backwards-incompatible
An argument changed type. This is backwards
incompatible as all existing call sites and signal
handlers need to be changed to use the new type.
This might have been caused by another argument
being added or removed; so check other error
messages too.
c-symbol-changed
info
The C symbol corresponding to a D-Bus API changed
name. This does not affect stability of the D-Bus
API, but may change the API of any generated C
bindings. You should check whether that is the
case.
deprecated
info
A D-Bus API has been marked as deprecated using the
org.freedesktop.DBus.Deprecated annotation.
The API can still be used, but you might want to
port applications away from using it, to use the
documented replacement instead.
ecs-changed-true-false,
ecs-changed-true-const,
ecs-changed-invalidates-false,
ecs-changed-invalidates-const
forwards-incompatible
A D-Bus property which previously emitted the
org.freedesktop.DBus.Properties.PropertiesChanged
signal when its value changed will no longer emit
that signal. It either now has a constant value (for
the lifetime of the D-Bus object containing the
property), or simply is no longer guaranteed to emit
the signal when its value changes. Applications
which previously handled notifications for this
property will no longer receive them, but will
otherwise continue to function.
ecs-changed-false-true,
ecs-changed-false-invalidates,
ecs-changed-const-true,
ecs-changed-const-invalidates
backwards-incompatible
A D-Bus property which previously did not emit the
org.freedesktop.DBus.Properties.PropertiesChanged
signal will now emit that signal when its value
changes. The signal may contain the updated value of
the property, or may simply mark the property as
‘invalidated’. Applications which previously assumed
the property had a constant value should be changed
to handle notifications for it.
ecs-changed-true-invalidates
backwards-incompatible
A D-Bus property which previously emitted its
updated value in the
org.freedesktop.DBus.Properties.PropertiesChanged
signal when its value changed will no longer include
the updated value, and will instead notify the
property as ‘invalidated’. Applications which
expected the property to be listed in the
changed_properties argument of the
org.freedesktop.DBus.Properties.PropertiesChanged
signal should be changed to look in the
invalidated_properties argument — or to
look in both, for maximum compatibility.
ecs-changed-invalidates-true
backwards-incompatible
A D-Bus property which previously did not emit its
updated value in the
org.freedesktop.DBus.Properties.PropertiesChanged
signal when its value changed will now include
the updated value, and will no longer notify the
property as ‘invalidated’. Applications which
expected the property to be listed in the
invalidated_properties argument of the
org.freedesktop.DBus.Properties.PropertiesChanged
signal should be changed to look in the
changed_properties argument — or to
look in both, for maximum compatibility.
ecs-changed-const-false
backwards-incompatible
A D-Bus property which was previously marked as
constant, with a value which did not change
throughout the lifetime of the containing D-Bus
object, is now marked as non-constant. Its value may
now change; however, such changes will not be
notified using the
org.freedesktop.DBus.Properties.PropertiesChanged
signal. Applications should be changed to re-query
the property when appropriate, and to no longer
assume that its value is constant.
ecs-changed-false-const
forwards-incompatible
A D-Bus property which was previously marked as
non-constant, is now marked as constant, with a
value which cannot change throughout the lifetime
of the containing D-Bus object. The property will
continue to not be notified through the
org.freedesktop.DBus.Properties.PropertiesChanged
signal. Applications may be changed to assume the
property’s value is constant, and hence no longer
need to re-query its value after the initial
query.
interface-added
forwards-incompatible
A new D-Bus interface was added to the API, and can
now be used in applications. You must bump your
application’s dependency on the D-Bus API version,
or check that the new interface is implemented at
runtime before using it, otherwise your application
will not run correctly against older versions of the
D-Bus service.
interface-removed
backwards-incompatible
A D-Bus interface was removed from the API, and can
no longer be used in applications. This is a
backwards-incompatible change, and all applications
which were using that interface must be ported away
from it, or have runtime version checks added so
they only use the interface when the D-Bus service
implements it at runtime.
method-added
forwards-incompatible
A new method was added to a D-Bus interface in the
API, and can now be used in applications. You must
bump your application’s dependency on the D-Bus API
version, or check that the new method is available
at runtime before using it, otherwise your
application will not run correctly against older
versions of the D-Bus service.
method-removed
backwards-incompatible
A method was removed from a D-Bus interface in the
API, and can no longer be used in applications. This
is a backwards-incompatible change, and all
applications which were using that method must be
ported away from it, or have runtime version checks
added so they only use the method when the D-Bus
service implements it at runtime.
property-access-changed-read-readwrite,
property-access-changed-write-readwrite
forwards-incompatible
A property changed access flags from ‘read’ or
‘write’ to ‘readwrite’, making it less restrictive.
Applications may now get and set the
property, which was not possible before. This is a
forwards-incompatible change, so any applications
which start using the lessened restrictions should
gracefully handle failure to use them, so that
they continue to work with older versions of the
D-Bus service.
property-access-changed-read-write,
property-access-changed-write-read,
property-access-changed-readwrite-read,
property-access-changed-readwrite-write
backwards-incompatible
A property changed access flags to be more
restrictive. This is a backwards incompatible
change, and applications will need to be changed to
stop accessing the property how they were before, or
to gracefully handle failure of set or get calls on
the property.
property-added
forwards-incompatible
A new property was added to a D-Bus interface in the
API, and can now be used in applications. You must
bump your application’s dependency on the D-Bus API
version, or check that the new property is available
at runtime before using it, otherwise your
application will not run correctly against older
versions of the D-Bus service.
property-removed
backwards-incompatible
A property was removed from a D-Bus interface in the
API, and can no longer be used in applications. This
is a backwards-incompatible change, and all
applications which were using that property must be
ported away from it, or have runtime version checks
added so they only use the property when the D-Bus
service implements it at runtime.
property-type-changed
backwards-incompatible
A property changed type. This is backwards
incompatible as all existing set and get sites need
to be changed to use the new type.
reply-added
backwards-incompatible
A D-Bus method which was previously marked as not
returning a reply will now return a reply. All call
sites using the method should be modified to
asynchronously wait for a reply and handle any
errors or return values it provides. This may be
coupled with a change in the method’s type to add
new ‘out’ arguments
(argument-added ),
which should be handled as appropriate.
reply-removed
backwards-incompatible
A D-Bus method which was previously marked as
returning a reply will now not return a reply. All
call sites using the method should be modified to
not wait for a reply, and any code which handles
errors or return values can be removed. This may be
coupled with a change in the method’s type to remove
‘out’ arguments
(argument-removed ),
support for which should be removed as
appropriate.
signal-added
forwards-incompatible
A new signal was added to a D-Bus interface in the
API, and can now be connected to in applications.
You must bump your application’s dependency on the
D-Bus API version, or check that the new signal is
available at runtime before connecting to it,
otherwise your application will not run correctly
against older versions of the D-Bus service.
signal-removed
backwards-incompatible
A signal was removed from a D-Bus interface in the
API, and can no longer be used in applications. This
is a backwards-incompatible change, and all
applications which were connecting to that signal
must be ported away from it, or have runtime version
checks added so they only connect to the signal when
the D-Bus service implements it at runtime.
undeprecated
info
A D-Bus API which was previously marked as
deprecated using the
org.freedesktop.DBus.Deprecated annotation
is no longer marked as deprecated. You may start
using it in applications again, although you should
check whether the un-deprecation was intentional
first.
Bugs and problems
If you have found a bug on this page, have found missing documentation, or
if some of the documentation is unclear, please
file a bug .
dbus-deviation-0.6.0/website/index.html 0000664 0001750 0001750 00000011116 13026466535 020701 0 ustar philip philip 0000000 0000000
dbus-deviation: a project for parsing D-Bus introspection XML
and processing it in various ways
dbus-deviation: a project for parsing D-Bus introspection XML and processing
it in various ways
dbus-deviation is a project for parsing D-Bus introspection XML and
processing it in various ways. Its main tool is dbus-interface-diff, which
calculates the difference between two D-Bus APIs for the purpose of checking
for API breaks. This functionality is also available as a Python module,
dbusdeviation.
A second Python module, dbusapi, is provided for parsing D-Bus introspection
XML to produce an AST representing a D-Bus interface.
dbus-deviation is licenced under the
LGPLv2.1+ .
Versions 0.4.0 and earlier were licenced under the same licence as the
D-Bus
project : your choice of the
Academic Free License version 2.1 ,
or the
GPLv2+ .
For version 0.5.0, the licence was changed to LGPLv2.1+ as it’s a more standard licence with simpler implications.
Download and installation
Tarballs for released versions of dbus-deviation are below. All have been
signed with key 4B2EAFA0 (Philip Withnall).
Building and installing follows the standard Python setuptools process:
tar -xzf ./dbus-deviation-$VERSION .tar.gz
cd ./dbus-deviation-$VERSION
./setup.py build
./setup.py --prefix=$PREFIX install
Documentation
For installation and quick-start information, see the
README
file .
API documentation is
available on pythonhosted ,
or
available here .
Details on all errors produced by dbus-deviation is
available
here .
Source code and bugs
Git repository
The source code for dbus-deviation is stored in git, which can be
viewed online or
cloned:
git clone https://github.com/pwithnall/dbus-deviation.git
Bugs
dbus-deviation uses
Bugs
Everywhere for feature enhancement tracking, so to
view all currently open bugs, clone the dbus-deviation git
repository and run:
be list
To file a bug, please contact the authors by
e-mail.
dbus-deviation is written by Philip Withnall, as part of R&D work by
Collabora .
If you have any bug reports, questions, suggestions or criticism of the
project, please e-mail me (philip tecnocode co uk).
dbus-deviation-0.6.0/dbus-deviation.mk 0000664 0001750 0001750 00000020166 13026466535 020515 0 ustar philip philip 0000000 0000000 # Copyright © 2015 Collabora Ltd.
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
# This Makefile can be included in the top-level Makefile.am in a project to
# provide rules for checking the API compatibility of D-Bus interface between
# different tagged releases of a project.
#
# To add it to your project, copy this file into your project, add it to git,
# and add the following lines to your top-level Makefile.am:
#
# dbus_api_xml_files = list of D-Bus interface XML files
# dbus_api_checkflags = --fatal-warnings
# -include $(top_srcdir)/dbus-deviation.mk
#
# Do not list this file in EXTRA_DIST or similar — it is designed to be used
# from git checkouts only. Further configuration options are documented below.
#
# Then, run the following command to initialise the database of past versions
# of the XML files:
#
# make dbus-deviation-mk-install
#
# It is safe to run this command multiple times, though there is no need to.
# It will not make changes to your remote repository, but will give you a
# `git push` command to run to push the database to a remote.
#
# Finally, copy pre-push.hook to .git/hooks/pre-push and ensure it’s
# executable. This script will automatically update the API signature database
# when a new release tag is pushed to the git remote. It is required for dist
# to succeed.
#
# If your project builds D-Bus interfaces at runtime, rather than automatically
# generating the code for them from XML files, you must populate the database
# manually. For each tagged release, and for each D-Bus interface, run:
#
# xml_file=path/to/generated-api-description.xml
# tag=release_tag_name
# dbus_api_git_refs=notes/dbus/api # matches the Makefile config
# git_remote_origin=origin # matches the Makefile config
#
# git checkout "$tag"
# # Build your program.
# # Run your program.
# # Call the org.freedesktop.DBus.Introspectable.Introspect() method.
# # Save its output to $xml_file.
#
# notes=$(git hash-object -w "$xml_file")
# xml_basename=$(basename "$xml_file")
# git notes --ref "refs/$(dbus_api_git_refs)/$xml_basename" \
# add -C "$notes" "$tag"
# git push "$(git_remote_origin)" refs/$(dbus_api_git_refs)/*
#
# The following configuration variables are mandatory:
#
# dbus_api_xml_files:
# Space-separated list of paths to the API XML files.
#
# The following are optional:
#
# git_remote_origin (default: origin):
# Remote to push/pull the API signature database to/from.
# dbus_api_diff_warnings (default: all):
# Comma-separated list of warnings to enable when running
# dbus-interface-diff.
# dbus_api_checkflags (default: empty):
# Flags to pass to the ‘check’ operation of dbus-interface-vcs-helper.
# Typically, this should be --fatal-warnings.
# dbus_api_distflags (default: empty):
# Flags to pass to the ‘dist’ operation of dbus-interface-vcs-helper.
# dbus_api_git_refs (default: notes/dbus/api):
# Path beneath refs/ where the git notes will be stored containing the
# API signatures database.
# GIT (default: git):
# Path of the git program to run, including any custom arguments to
# pass to every invocation of git.
#
# The Makefile hooks in to dist-hook and check-local to update the API
# signature database and to check for differences between the most recent
# release and the current working tree.
#
# This file support out-of-tree builds, and git repositories with non-standard
# GIT_WORK_TREE or GIT_DIR settings (bare repositories).
# Mandatory configuration.
dbus_api_xml_files ?=
# Optional configuration.
git_remote_origin ?= origin
dbus_api_diff_warnings ?= info,forwards-compatibility,backwards-compatibility
dbus_api_checkflags ?=
dbus_api_distflags ?=
dbus_api_git_refs ?= notes/dbus/api
dbus_api_git_work_tree ?= $(top_srcdir)
dbus_api_git_dir ?= $(dbus_api_git_work_tree)/.git
GIT = git
# Silent rules for dbus-interface-vcs-helper
dbus_interface_vcs_helper_v = $(dbus_interface_vcs_helper_v_$(V))
dbus_interface_vcs_helper_v_ = $(dbus_interface_vcs_helper_v_$(AM_DEFAULT_VERBOSITY))
dbus_interface_vcs_helper_v_0 = --silent
dbus_interface_vcs_helper_v_1 =
V_api = $(v_api_$(V))
v_api_ = $(v_api_$(AM_DEFAULT_VERBOSITY))
v_api_0 = @echo " API " $@;
v_api_1 =
# For each XML file in $(dbus_api_xml_files), add it to the API signature
# database for the most recent git tag.
dist-dbus-api-compatibility:
$(V_api)dbus-interface-vcs-helper $(dbus_interface_vcs_helper_v) \
--git "$(GIT)" \
--git-dir "$(dbus_api_git_dir)" \
--git-work-tree "$(dbus_api_git_work_tree)" \
--git-refs "$(dbus_api_git_refs)" \
--git-remote "$(git_remote_origin)" \
dist --ignore-existing $(dbus_api_distflags) \
$(dbus_api_xml_files)
# Check the pre-push hook is installed, otherwise the API signature database
# will not get pushed to the remote after the release tag is created.
dist-dbus-api-compatibility-check-hook:
@if [ ! -x "$(dbus_api_git_dir)/hooks/pre-push" ] || \
! grep dbus-interface-vcs-helper "$(dbus_api_git_dir)/hooks/pre-push"; then \
echo "error: dbus-deviation git hook is not installed. Copy pre-push.hook to" 1>&2; \
echo " $(dbus_api_git_dir)/hooks/pre-push" 1>&2; \
echo " to enable updates to the D-Bus API signature database." 1>&2; \
echo " See dbus-deviation.mk for more details." 1>&2; \
echo "Aborting." 1>&2; \
exit 1; \
fi
dist-hook: dist-dbus-api-compatibility dist-dbus-api-compatibility-check-hook
.PHONY: dist-dbus-api-compatibility dist-dbus-api-compatibility-check-hook
# Check that the D-Bus API signatures for the two refs given as OLD_REF and
# NEW_REF are compatible; error if they are not. The refs should typically be
# tags, specified as ‘tag_name^{tag}’, since the API signature database is not
# stored for non-tag refs by default.
#
# OLD_REF defaults to the most recent git tag. NEW_REF defaults to the current
# working tree — so running this rule with neither specified will check the
# current working tree against the latest release. If this happens during
# distcheck, it effectively checks the release-in-progress against the
# previous release, which is exactly what is expected of distcheck.
#
# If this is run on a source directory where $(dbus_api_git_dir) does not
# exist, it will print a message and exit successfully. This supports the use
# case where `make distcheck` is run on an extracted tarball of a release.
check-dbus-api-compatibility:
$(V_api)dbus-interface-vcs-helper $(dbus_interface_vcs_helper_v) \
--git "$(GIT)" \
--git-dir "$(dbus_api_git_dir)" \
--git-work-tree "$(dbus_api_git_work_tree)" \
--git-refs "$(dbus_api_git_refs)" \
--git-remote "$(git_remote_origin)" \
check \
--diff-warnings "$(dbus_api_diff_warnings)" \
$(dbus_api_checkflags) \
"$(OLD_REF)" "$(NEW_REF)"
check-local: check-dbus-api-compatibility
.PHONY: check-dbus-api-compatibility
# Installation rule to set up an API signature database for each existing tag.
# It is safe to run this multiple times.
dbus-deviation-mk-install:
$(V_api)dbus-interface-vcs-helper $(dbus_interface_vcs_helper_v) \
--git "$(GIT)" \
--git-dir "$(dbus_api_git_dir)" \
--git-work-tree "$(dbus_api_git_work_tree)" \
--git-refs "$(dbus_api_git_refs)" \
--git-remote "$(git_remote_origin)" \
--no-push \
install $(dbus_api_xml_files)
.PHONY: dbus-deviation-mk-install
# Helper for the pre-push git hook to export the configuration.
# Exports in bash syntax so this can be directly evaled.
dbus-deviation-mk-config:
@echo "dbus_api_git_refs=\"$(dbus_api_git_refs)\""
@echo "dbus_api_xml_files=\"$(dbus_api_xml_files)\""
.PHONY: dbus-deviation-mk-config
dbus-deviation-0.6.0/dbusapi/ 0000775 0001750 0001750 00000000000 13035747236 016671 5 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/dbusapi/interfaceparser.py 0000664 0001750 0001750 00000010134 13026466535 022417 0 ustar philip philip 0000000 0000000 # -*- coding: utf-8 -*-
#
# Copyright © 2015 Collabora Ltd.
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
Module providing an `InterfaceParser` object for parsing D-Bus introspection
XML files into abstract syntax trees (ASTs).
"""
# pylint: disable=no-member
from lxml import etree
from dbusapi.ast import AstLog, Node, TP_DTD
def _skip_non_node(elem):
for node in elem.getchildren():
if node.tag == 'node':
return node
return None
class ParsingLog(AstLog):
"""A specialized AstLog subclass for parsing issues"""
def __init__(self, filename):
"""
Construct a new ParsingLog.
Args:
filename: str, the name of the file being parsed.
"""
super(ParsingLog, self).__init__()
self.__filename = filename
self.domain = 'parser'
def _create_entry(self, code, message):
return self.__filename, self.domain, code, message
class InterfaceParser(object):
"""
Parse a D-Bus introspection XML file.
This validates the file, but not exceedingly strictly. It is a pragmatic
parser, designed for use by the InterfaceComparator rather than more
general code. It ignores certain common extensions found in introspection
XML files, such as documentation elements, but will fail on other
unrecognised elements.
"""
def __init__(self, filename):
"""
Construct a new InterfaceParser.
Args:
filename: path to the XML introspection file to parse
"""
self._filename = filename
self._log = ParsingLog(filename)
@staticmethod
def get_output_codes():
"""Return a list of all possible output codes."""
return ParsingLog(None).issue_codes
def get_output(self):
"""Return a list of all logged parser messages."""
return self._log.issues
def _get_root(self):
root = etree.parse(self._filename).getroot()
# Handle specifications wrapped in tp:spec.
if root.tag == '{%s}spec' % TP_DTD:
root = _skip_non_node(root)
if root is not None and root.tag != 'node':
self._log.log_issue('unknown-node',
'Unknown root node ‘%s’.' % root.tag)
root = _skip_non_node(root)
return root
def parse_with_nodes(self):
"""
Parse the introspection XML file and build an AST.
Returns:
An ast.Node instance, representing the root node.
If parsing fails, None is returned.
"""
self._log.clear()
root = self._get_root()
if root is None:
return None
root_node = Node.from_xml(root, None, self._log)
if root_node.name and \
not Node.is_valid_absolute_object_path(root_node.name):
self._log.log_issue('node-name',
'Root node name is not an absolute object '
'path ‘%s’.' % root_node.name)
if self._log.issues:
return None
return root_node
def parse(self):
"""
Parse the introspection XML file and build an AST.
Returns:
A non-empty dict of interfaces belonging to the root node in the
file, mapping each interface name to an ast.Interface instance.
If parsing fails, None is returned.
"""
root_node = self.parse_with_nodes()
interfaces = root_node.interfaces if root_node else None
return interfaces
dbus-deviation-0.6.0/dbusapi/types.py 0000664 0001750 0001750 00000021202 13035124677 020402 0 ustar philip philip 0000000 0000000 # -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4
#
# Copyright © 2016 Kaloyan Tenchov
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
A representation of the D-Bus type system as a series of classes which can be
built into an abstract syntax tree (AST) for representing complex (nested)
types.
An AST can be built by parsing a D-Bus type signature (using
`typeparser.TypeParser`) or by building the tree of objects manually.
"""
from abc import ABCMeta, abstractmethod
# pylint: disable=too-few-public-methods
class Type(object):
"""
An abstract class - AST representation of a D-Bus type.
See http://dbus.freedesktop.org/doc/dbus-specification.html#type-system
"""
__metaclass__ = ABCMeta
def __init__(self):
"""Constructor."""
self.type = "\0"
self.name = "INVALID"
self.alignment = 1
def __str__(self):
"""Format the type as a human-readable string."""
return self.type
def __eq__(self, other):
return (isinstance(other, self.__class__) and
self.__dict__ == other.__dict__)
def __ne__(self, other):
return not self.__eq__(other)
# pylint: disable=too-few-public-methods
class Byte(Type):
"""
AST representation of the D-Bus BYTE type.
8-bit unsigned integer.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "y"
self.name = "BYTE"
self.alignment = 1
# pylint: disable=too-few-public-methods
class Boolean(Type):
"""
AST representation of the D-Bus BOOLEAN type.
Boolean value, 0 is FALSE and 1 is TRUE. Everything else is invalid.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "b"
self.name = "BOOLEAN"
self.alignment = 4
# pylint: disable=too-few-public-methods
class Int16(Type):
"""
AST representation of the D-Bus INT16 type.
16-bit signed integer.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "n"
self.name = "INT16"
self.alignment = 2
# pylint: disable=too-few-public-methods
class UInt16(Type):
"""
AST representation of the D-Bus UINT16 type.
16-bit unsigned integer.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "q"
self.name = "UINT16"
self.alignment = 2
# pylint: disable=too-few-public-methods
class Int32(Type):
"""
AST representation of the D-Bus INT32 type.
32-bit signed integer.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "i"
self.name = "INT32"
self.alignment = 4
# pylint: disable=too-few-public-methods
class UInt32(Type):
"""
AST representation of the D-Bus UINT32 type.
32-bit unsigned integer.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "u"
self.name = "UINT32"
self.alignment = 4
# pylint: disable=too-few-public-methods
class Int64(Type):
"""
AST representation of the D-Bus INT64 type.
64-bit signed integer.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "x"
self.name = "INT64"
self.alignment = 8
# pylint: disable=too-few-public-methods
class UInt64(Type):
"""
AST representation of the D-Bus UINT64 type.
64-bit unsigned integer.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "t"
self.name = "UINT64"
self.alignment = 8
# pylint: disable=too-few-public-methods
class Double(Type):
"""
AST representation of the D-Bus DOUBLE type.
IEEE 754 double.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "d"
self.name = "DOUBLE"
self.alignment = 8
# pylint: disable=too-few-public-methods
class String(Type):
"""
AST representation of the D-Bus STRING type.
UTF-8 string (must be valid UTF-8). Must be nul terminated and contain no
other nul bytes.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "s"
self.name = "STRING"
self.alignment = 4
# pylint: disable=too-few-public-methods
class ObjectPath(Type):
"""
AST representation of the D-Bus OBJECT_PATH type.
Name of an object instance.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "o"
self.name = "OBJECT_PATH"
self.alignment = 4
# pylint: disable=too-few-public-methods
class Signature(Type):
"""
AST representation of the D-Bus SIGNATURE type.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "g"
self.name = "SIGNATURE"
self.alignment = 1
# pylint: disable=too-few-public-methods
class Variant(Type):
"""
AST representation of the D-Bus VARIANT type.
Variant type - the type of the value is part of the value itself.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "v"
self.name = "VARIANT"
self.alignment = 1
# pylint: disable=too-few-public-methods
class UnixFD(Type):
"""
AST representation of the D-Bus UNIX_FD type.
Unix file descriptor.
"""
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.type = "h"
self.name = "UNIX_FD"
self.alignment = 4
# pylint: disable=too-few-public-methods
class Container(Type):
"""
An abstract class - AST representation of the D-Bus container type.
"""
__metaclass__ = ABCMeta
def __init__(self):
"""Constructor."""
Type.__init__(self)
self.members = []
@abstractmethod
def __str__(self):
"""Format the type as a human-readable string."""
pass
# pylint: disable=too-few-public-methods
class Array(Container):
"""
AST representation of the D-Bus ARRAY type.
"""
def __init__(self):
"""Constructor."""
Container.__init__(self)
self.type = "a"
self.name = "ARRAY"
self.alignment = 4
def __str__(self):
"""Format the type as a human-readable string."""
assert len(self.members) == 1
return "{}{}".format(self.type, self.members[0])
# pylint: disable=too-few-public-methods
class Struct(Container):
"""
AST representation of the D-Bus STRUCT type.
"""
def __init__(self):
"""Constructor."""
Container.__init__(self)
self.type = "r"
self.name = "STRUCT"
self.alignment = 8
def __str__(self):
"""Format the type as a human-readable string."""
return "({})".format("".join(map(str, self.members)))
# pylint: disable=too-few-public-methods
class DictEntry(Container):
"""
AST representation of the D-Bus DICT_ENTRY type.
"""
def __init__(self):
"""Constructor."""
Container.__init__(self)
self.type = "e"
self.name = "DICT_ENTRY"
self.alignment = 8
def __str__(self):
"""Format the type as a human-readable string."""
assert len(self.members) == 2
return "{{{}{}}}".format(self.members[0], self.members[1])
# pylint: disable=too-few-public-methods
class TypeSignature(object):
"""
AST representation of a D-Bus signature - an ordered list of one or more
types.
See http://dbus.freedesktop.org/doc/dbus-specification.html#type-system
"""
def __init__(self):
"""Constructor."""
self.members = []
def __str__(self):
"""Format the type as a human-readable string."""
return "".join(map(str, self.members))
def __eq__(self, other):
return (isinstance(other, self.__class__) and
self.__dict__ == other.__dict__)
def __ne__(self, other):
return not self.__eq__(other)
dbus-deviation-0.6.0/dbusapi/typeparser.py 0000664 0001750 0001750 00000015456 13035125005 021434 0 ustar philip philip 0000000 0000000 # -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4
#
# Copyright © 2016 Kaloyan Tenchov
# Copyright © 2017 Philip Withnall
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
Module providing a `TypeParser` object for parsing D-Bus type strings into
abstract syntax trees (ASTs) representing the nested type structure.
"""
from dbusapi import types
from dbusapi.log import Log
class TypeParsingLog(Log):
"""Specialized Log subclass for type parsing messages"""
def __init__(self):
"""Construct a new TypeParsingLog"""
super(TypeParsingLog, self).__init__()
self.register_issue_code('invalid-type')
self.register_issue_code('reserved-type')
self.register_issue_code('unknown-type')
self.domain = 'types'
class TypeParser(object):
"""
Parse and validate a D-Bus type string.
The D-Bus type system and type strings are explained in detail in the
D-Bus specification:
https://dbus.freedesktop.org/doc/dbus-specification.html#type-system
and in the GVariant documentation for GLib:
https://developer.gnome.org/glib/stable/glib-GVariantType.html#id-1.6.18.6.9
"""
def __init__(self, signature):
"""
Construct a new TypeParser.
Args:
signature: a D-Bus type string
"""
self.signature = signature
self._log = TypeParsingLog()
self._index = 0
@staticmethod
def get_output_codes():
"""Return a list of all possible output codes."""
return TypeParsingLog().issue_codes
def get_output(self):
"""Return a list of all logged parser messages."""
return self._log.issues
def _get_next_character(self):
"""Return the next character from the signature."""
if self._index < len(self.signature):
character = self.signature[self._index]
self._index += 1
else:
character = None
return character
# pylint: disable=too-many-return-statements,too-many-branches
def _parse_one_type(self, character):
"""Parse one complete type from the signature."""
basic_types = {
'y': types.Byte,
'b': types.Boolean,
'n': types.Int16,
'q': types.UInt16,
'i': types.Int32,
'u': types.UInt32,
'x': types.Int64,
't': types.UInt64,
'd': types.Double,
's': types.String,
'o': types.ObjectPath,
'g': types.Signature,
'v': types.Variant,
'h': types.UnixFD,
}
if character in basic_types:
return basic_types[character]()
elif character == "a":
out_array = types.Array()
character = self._get_next_character()
if not character:
self._log.log_issue('invalid-type',
'Incomplete array declaration.')
return None
one_type = self._parse_one_type(character)
if not one_type:
# Invalid member type - error has already been logged.
return None
out_array.members.append(one_type)
return out_array
elif character == "(":
out_struct = types.Struct()
while True:
character = self._get_next_character()
if not character or \
(character == ')' and len(out_struct.members) == 0):
self._log.log_issue('invalid-type',
'Incomplete structure declaration.')
return None
elif character == ")":
break
one_type = self._parse_one_type(character)
if not one_type:
# Invalid member type - error has already been logged.
return None
out_struct.members.append(one_type)
return out_struct
elif character == "{":
out_dict = types.DictEntry()
while True:
character = self._get_next_character()
if not character or \
(character == '}' and len(out_dict.members) != 2):
self._log.log_issue('invalid-type',
'Incomplete dictionary declaration.')
return None
elif character == "}":
break
one_type = self._parse_one_type(character)
if not one_type:
# Invalid member type - error has already been logged.
return None
if len(out_dict.members) >= 2:
self._log.log_issue('invalid-type',
'Invalid dictionary declaration.')
return None
out_dict.members.append(one_type)
return out_dict
elif character in ['r', 'e', 'm', '*', '?', '@', '&', '^']:
# https://dbus.freedesktop.org/doc/dbus-specification.html#idm399
self._log.log_issue('reserved-type',
'Reserved type ‘%s’ must not be used in '
'signatures on D-Bus.' % character)
return None
else:
self._log.log_issue('unknown-type',
'Unknown type ‘%s’.' % character)
return None
def parse(self):
"""
Parse the type string and build an AST of the type
Returns:
A non-empty list of types.
If parsing fails, or if the input string is empty,
None is returned.
"""
self._log.clear()
self._index = 0
if len(self.signature) == 0:
self._log.log_issue('invalid-type', 'Empty type string.')
out = types.TypeSignature()
while True:
character = self._get_next_character()
if not character:
break
one_type = self._parse_one_type(character)
if not one_type:
# Invalid type
break
out.members.append(one_type)
if self._log.issues:
return None
return out
dbus-deviation-0.6.0/dbusapi/__init__.py 0000664 0001750 0001750 00000000000 13026466535 020770 0 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/dbusapi/log.py 0000664 0001750 0001750 00000003363 13026466535 020031 0 ustar philip philip 0000000 0000000 # -*- coding: utf-8 -*-
#
# Copyright © 2015, 2016 Collabora Ltd.
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
An implementation of a base logging class.
"""
class Log(object):
"""Base logging class."""
def __init__(self):
"""Construct a new Log"""
self.issues = []
self.issue_codes = set()
self.domain = 'default'
def register_issue_code(self, code):
"""
Register a new issue code.
Duplicate codes will be silently ignored.
Args:
code: str, an issue code, for example `unknown-node`
"""
self.issue_codes.add(code)
def log_issue(self, code, message):
"""
Log a new issue.
Args:
code: str, A registered code for that issue.
message: str, A message describing the issue.
"""
assert code in self.issue_codes
self.issues.append(self._create_entry(code, message))
# pylint: disable=no-self-use
def _create_entry(self, code, message):
return None, self.domain, code, message
def clear(self):
"""Clear the issue list."""
self.issues = []
dbus-deviation-0.6.0/dbusapi/typeformatter.py 0000664 0001750 0001750 00000007646 13035744274 022164 0 ustar philip philip 0000000 0000000 # -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4
#
# Copyright © 2017 Philip Withnall
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
Module providing a `BasicTypeFormatter` object for formatting D-Bus type
abstract syntax trees (ASTs) into human-readable strings describing the type
structure.
These strings are intended to be easy to read for a technical reader, but
without having to memorise the single-character D-Bus types. They could be used
in documentation, for example.
Other type formatters may be added in future which format the type ASTs
differently.
"""
from dbusapi import types
class BasicTypeFormatter(object):
"""
Format a D-Bus type AST as a string.
Given a valid D-Bus type structure (see types.Type and
types.TypeSignature), format it as a human-readable string. The format
mirrors that used by D-Feet when introspecting D-Bus interfaces.
In future, support may be added to this class for choosing the output
language for the formatted strings.
"""
def format(self, type_instance):
"""
Format the type instance as a human-readable string.
Note that the type instance must be a valid type structure. For
example, this means that any types.Struct instance must have one or
more child members; any types.DictEntry instance must have exactly two
child members (key and value) and be the immediate child of a
types.Array instance.
See the D-Bus documentation for the full details of type system
restrictions:
https://dbus.freedesktop.org/doc/dbus-specification.html#type-system
The type instance may be a types.Type instance or a types.TypeSignature
instance.
Args:
type_instance: valid types.Type or types.TypeSignature instance
"""
basic_type_map = {
types.Byte: 'Byte',
types.Boolean: 'Boolean',
types.Int16: 'Int16',
types.UInt16: 'UInt16',
types.Int32: 'Int32',
types.UInt32: 'UInt32',
types.Int64: 'Int64',
types.UInt64: 'UInt64',
types.Double: 'Double',
types.String: 'String',
types.ObjectPath: 'Object Path',
types.Signature: 'Signature',
types.Variant: 'Variant',
types.UnixFD: 'Unix FD',
}
# pylint: disable=unidiomatic-typecheck
if isinstance(type_instance, types.TypeSignature):
return ', '.join(map(self.format, type_instance.members))
elif (isinstance(type_instance, types.Array) and
isinstance(type_instance.members[0], types.DictEntry)):
dictionary = type_instance.members[0]
key = self.format(dictionary.members[0])
value = self.format(dictionary.members[1])
return 'Dict of {{{}: {}}}'.format(key, value)
elif isinstance(type_instance, types.Array):
member = self.format(type_instance.members[0])
return 'Array of [{}]'.format(member)
elif isinstance(type_instance, types.Struct):
members = map(self.format, type_instance.members)
return 'Struct of ({})'.format(', '.join(members))
elif type(type_instance) in basic_type_map:
return basic_type_map[type(type_instance)]
else:
assert False
dbus-deviation-0.6.0/dbusapi/ast.py 0000664 0001750 0001750 00000056524 13035123314 020027 0 ustar philip philip 0000000 0000000 # -*- coding: utf-8 -*-
#
# Copyright © 2015, 2016 Collabora Ltd.
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
An implementation of the abstract syntax tree (AST) for a D-Bus introspection
document, which fully describes a D-Bus API.
An AST can be built by parsing an XML file (using
`interfaceparser.InterfaceParser`) or by building the tree of objects manually.
"""
from abc import ABCMeta
from collections import OrderedDict
# pylint: disable=no-member
from lxml import etree
from re import match
from dbusapi.log import Log
from dbusapi.typeparser import TypeParser
TP_DTD = 'http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0'
FDO_DTD = 'http://www.freedesktop.org/dbus/1.0/doc.dtd'
class AstLog(Log):
"""Specialized Log subclass for AST messages"""
def __init__(self):
"""Construct a new AstLog"""
super(AstLog, self).__init__()
self.register_issue_code('unknown-node')
self.register_issue_code('empty-root')
self.register_issue_code('missing-attribute')
self.register_issue_code('duplicate-node')
self.register_issue_code('duplicate-interface')
self.register_issue_code('duplicate-method')
self.register_issue_code('duplicate-signal')
self.register_issue_code('duplicate-property')
self.register_issue_code('node-name')
self.register_issue_code('interface-name')
self.register_issue_code('method-name')
self.register_issue_code('signal-name')
self.register_issue_code('property-type')
self.register_issue_code('argument-type')
self.domain = 'ast'
def ignore_node(node):
"""Decide whether to ignore the given node when parsing."""
return node.tag[0] == '{' # in a namespace
# pylint: disable=too-many-instance-attributes
class BaseNode(object):
"""Base class for all D-Bus AST nodes."""
__metaclass__ = ABCMeta
DOCSTRING_TAGS = ['{%s}docstring' % TP_DTD,
'{%s}doc' % FDO_DTD]
required_attributes = ['name']
optional_attributes = []
def __init__(self, name, annotations=None, log=None):
"""Construct a new ast.BaseNode
Args:
name: str, the name of the node.
annotations: potentially empty dict of annotations applied
to the node, mapping annotation name to an `ast.Annotation`
instance
log: subclass of `Log`, used to store log messages; can be None
"""
self.name = name
self.children = []
self.parent = None
self._comment = None
self.log = log or AstLog()
self.annotations = OrderedDict()
self.line_number = -1
self.comment_line_number = -1
self._children_types = {
'annotation': Annotation,
}
self._type_containers = {
Annotation: self.annotations,
}
for annotation in (annotations or {}).values():
self._add_child(annotation)
@classmethod
def from_xml(cls, node, comment, log, parent=None):
"""Return a new ast.BaseNode instance from an XML node."""
attrs = {}
valid = True
for attr_name in cls.required_attributes:
# Avoid redefining a builtin
member_name = attr_name
if member_name == 'type':
member_name = 'type_'
try:
attrs[member_name] = node.attrib[attr_name]
except KeyError:
log.log_issue('missing-attribute',
'Missing required attribute ‘%s’ in %s.' %
(attr_name, node.tag))
valid = False
if not valid:
return None
for attr_name in cls.optional_attributes:
attrs[attr_name] = node.attrib.get(attr_name)
# FIXME: Hack for the fact that Node.name and Argument.name are not
# actually required, but is the first attribute in the constructor, and
# hence must be specified. This can be removed when we break API.
if (cls == Node or cls == Argument) and 'name' not in attrs:
attrs['name'] = None
elif issubclass(cls, Callable) and 'args' not in attrs:
attrs['args'] = []
attrs['log'] = log
res = cls(**attrs)
res.line_number = node.sourceline
if comment is not None:
res.comment = comment.text
# lxml reports the last source line for xml comments as
# being the actual source line, fix this.
# Also report line numbers starting from 1, consistent
# with node.line_number
res.comment_line_number = (comment.sourceline -
len(res.comment.split('\n')) + 1)
if parent:
parent.add_child(res)
res.parse_xml_children(node)
return res
def add_child(self, child):
"""Add a child to the node"""
return self._add_child(child)
def parse_xml_children(self, node):
"""Parse the XML node's children."""
xml_comment = None
for elem in node:
if elem.tag == etree.Comment:
xml_comment = elem
continue
elif elem.tag in BaseNode.DOCSTRING_TAGS:
self.comment_line_number = elem.sourceline
self.comment = elem.text
continue
elif ignore_node(elem):
xml_comment = None
continue
try:
ctype = self._children_types[elem.tag]
except KeyError:
xml_comment = None
if isinstance(self, Node) and not self.pretty_name:
# Special handling for root nodes to allow more meaningful
# error messages.
self.__log_issue('unknown-node',
"Unknown node ‘%s’ in root." % elem.tag)
else:
self.__log_issue('unknown-node',
"Unknown node ‘%s’ in %s ‘%s’." %
(elem.tag, type(self).__name__.lower(),
self.pretty_name))
continue
ctype.from_xml(elem, xml_comment, parent=self,
log=self.log)
xml_comment = None
def walk(self):
"""Traverse this node's children in pre-order."""
for child in self.children:
yield child
for grandchild in child.walk():
yield grandchild
# Backward compat
def format_name(self):
"""Format this node's name as a human-readable string"""
return self.pretty_name
@property
def comment(self):
"""
Get the comment for this node.
Returns:
str: If the node was annotated with `org.gtk.GDBus.DocString`,
the value of the annotation, otherwise one of:
* A tp:docstring child node
* A doc:doc child node
* An XML comment immediately preceding the XML node.
, whichever is seen last.
"""
try:
doc_annotation = self.annotations['org.gtk.GDBus.DocString']
return doc_annotation.value
except KeyError:
return self._comment
@comment.setter
def comment(self, value):
"""Set the comment for this node."""
self._comment = value
@property
def pretty_name(self):
"""Format the node's name as a human-readable string."""
return self.name
def __log_issue(self, code, message):
self.log.log_issue(code, message)
def _child_is_duplicate(self, child):
return child.name in self._type_containers[type(child)]
def _add_child(self, child):
child.parent = self
if self._child_is_duplicate(child):
self.__log_issue('duplicate-%s' % type(child).__name__.lower(),
'Duplicate %s definition ‘%s’.' %
(type(child).__name__.lower(), child.pretty_name))
return False
self.children.append(child)
container = self._type_containers[type(child)]
if isinstance(container, list):
container.append(child)
else:
container[child.name] = child
return True
class Node(BaseNode):
"""
AST representation of a element.
This represents the top level of a D-Bus API.
"""
required_attributes = []
optional_attributes = ['name']
# pylint: disable=too-many-arguments
def __init__(self, name=None, interfaces=None, nodes=None,
annotations=None, log=None):
"""
Construct a new ast.Node.
Args:
name: node name; a non-empty string; The root should either
have no name or should have a name that is a valid absolute
object path. Child names must be valid relative paths.
interfaces: potentially empty dict of interfaces in the node,
mapping interface name to an `ast.Interface` instance
nodes: potentially empty dict of properties in the node,
mapping node name to an `ast.Node` instance
annotations: potentially empty dict of annotations applied to the
node, mapping annotation name to an `ast.Annotation` instance
log: subclass of `Log`, used to store log messages; can be None
"""
super(Node, self).__init__(name, annotations, log)
self._children_types.update({'interface': Interface,
'node': Node})
self.interfaces = OrderedDict()
self.nodes = OrderedDict()
self._type_containers.update({Interface: self.interfaces,
Node: self.nodes})
for child in (interfaces or {}).values():
self._add_child(child)
for child in (nodes or {}).values():
self._add_child(child)
def _add_child(self, child):
if isinstance(child, Node):
if not child.name:
self.log.log_issue('missing-attribute',
'Missing required attribute ‘name’ in '
'non-root node.')
elif not Node.is_valid_relative_object_path(child.name):
self.log.log_issue('node-name',
'Non-root node name is not a relative '
'object path ‘%s’.' % child.name)
child.node = self
return super(Node, self)._add_child(child)
@staticmethod
def is_valid_absolute_object_path(path):
"""
Validate an absolute D-Bus object path.
https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path
Args:
path: object path
"""
return path == '/' or match(r'(/[A-Za-z0-9_]+)+', path) is not None
@staticmethod
def is_valid_relative_object_path(path):
"""
Validate a relative D-Bus object path.
https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path
Args:
path: object path
"""
return match(r'[A-Za-z0-9_]+(/[A-Za-z0-9_]+)*', path) is not None
class Interface(BaseNode):
"""
AST representation of an element.
This represents the most commonly used node of a D-Bus API.
"""
# pylint: disable=too-many-arguments
def __init__(self, name, methods=None, properties=None,
signals=None, annotations=None, log=None):
"""
Construct a new ast.Interface.
Args:
name: interface name; a non-empty string
methods: potentially empty dict of methods in the interface,
mapping method name to an `ast.Method` instance
properties: potentially empty dict of properties in the interface,
mapping property name to an `ast.Property` instance
signals: potentially empty dict of signals in the interface,
mapping signal name to an `ast.Signal` instance
annotations: potentially empty dict of annotations applied to the
interface, mapping annotation name to an `ast.Annotation`
instance
log: subclass of `Log`, used to store log messages; can be None
"""
super(Interface, self).__init__(name, annotations, log)
if name and not Interface.is_valid_interface_name(name):
self.log.log_issue('interface-name',
'Invalid interface name ‘%s’.' % name)
self._children_types.update({'signal': Signal,
'method': Method,
'property': Property})
self.methods = OrderedDict()
self.signals = OrderedDict()
self.properties = OrderedDict()
self._type_containers.update({Method: self.methods,
Signal: self.signals,
Property: self.properties})
for child in (methods or {}).values():
self._add_child(child)
for child in (signals or {}).values():
self._add_child(child)
for child in (properties or {}).values():
self._add_child(child)
def _add_child(self, child):
child.interface = self
return super(Interface, self)._add_child(child)
@staticmethod
def is_valid_interface_name(name):
"""
Validate a D-Bus interface name.
http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-interface
Args:
name: interface name
"""
return len(name) <= 255 and \
match(r'[A-Za-z_][A-Za-z0-9_]*'
r'(\.[A-Za-z_][A-Za-z0-9_]*)+', name) is not None
def _dotted_name(elem):
if elem.parent:
return elem.parent.format_name() + '.' + elem.name
return elem.name
class Property(BaseNode):
"""
AST representation of a element.
This represents a readable or writable property of an interface.
"""
ACCESS_READ = 'read'
ACCESS_WRITE = 'write'
ACCESS_READWRITE = 'readwrite'
required_attributes = BaseNode.required_attributes + ['access', 'type']
# pylint: disable=too-many-arguments
def __init__(self, name, type_, access, annotations=None, log=None):
"""
Construct a new ast.Property.
Args:
name: property name; a non-empty string, not including the parent
interface name
type_: type string for the property; see http://goo.gl/uCpa5A
access: ACCESS_READ, ACCESS_WRITE, or ACCESS_READWRITE
annotations: potentially empty dict of annotations applied to the
property, mapping annotation name to an `ast.Annotation`
instance
log: subclass of `Log`, used to store log messages; can be None
"""
super(Property, self).__init__(name, annotations, log)
type_parser = TypeParser(type_)
self.type = type_parser.parse()
if self.type is None:
message = type_parser.get_output()[0][3]
self.log.log_issue('property-type',
'Error when parsing type ‘%s’ for property '
'‘%s’: %s' %
(type_, name, message))
self.access = access
self.interface = None
@property
def pretty_name(self):
"""Format the property's name as a human-readable string"""
return _dotted_name(self)
class Callable(BaseNode):
u"""
AST representation of a callable element.
This represents a ‘callable’, such as a method or a signal. All callables
contain a list of in and out arguments.
"""
def __init__(self, name, args, annotations=None, log=None):
"""
Construct a new ast.Callable.
Args:
name: callable name; a non-empty string, not including the parent
interface name
args: potentially empty ordered list of ast.Arguments accepted and
returned by the callable
annotations: potentially empty dict of annotations applied to the
callable, mapping annotation name to an ast.Annotation
instance
log: subclass of `Log`, used to store log messages; can be None
"""
super(Callable, self).__init__(name, annotations, log)
self.arguments = []
self.interface = None
self._children_types.update({'arg': Argument})
self._type_containers.update({Argument: self.arguments})
for arg in args:
self._add_child(arg)
def _child_is_duplicate(self, child):
if isinstance(child, Argument):
return False
return super(Callable, self)._child_is_duplicate(child)
@property
def pretty_name(self):
"""Format the callable's name as a human-readable string"""
return _dotted_name(self)
@staticmethod
def is_valid_name(name):
"""
Validate a D-Bus member name.
https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-member
Args:
name: callable name
"""
return len(name) <= 255 and \
match(r'[A-Za-z_][A-Za-z0-9_]*', name) is not None
class Method(Callable):
"""
AST representation of a element.
This represents a callable method of an interface.
"""
def __init__(self, name, args, annotations=None, log=None):
"""
Construct a new ast.Method.
Args:
name: method name; a non-empty string, not including the parent
interface name
args: potentially empty ordered list of ast.Arguments accepted and
returned by the method
annotations: potentially empty dict of annotations applied to the
method, mapping annotation name to an ast.Annotation
instance
log: subclass of `Log`, used to store log messages; can be None
"""
super(Method, self).__init__(name, args, annotations, log)
if name and not Callable.is_valid_name(name):
self.log.log_issue('method-name',
'Invalid method name ‘%s’.' % name)
class Signal(Callable):
"""
AST representation of a element.
This represents an emittable signal on an interface.
"""
def __init__(self, name, args, annotations=None, log=None):
"""
Construct a new ast.Signal.
Args:
name: signal name; a non-empty string, not including the parent
interface name
args: potentially empty ordered list of ast.Arguments accepted and
returned by the signal
annotations: potentially empty dict of annotations applied to the
signal, mapping annotation name to an ast.Annotation
instance
log: subclass of `Log`, used to store log messages; can be None
"""
super(Signal, self).__init__(name, args, annotations, log)
if name and not Callable.is_valid_name(name):
self.log.log_issue('signal-name',
'Invalid signal name ‘%s’.' % name)
class Argument(BaseNode):
"""
AST representation of an element.
This represents an argument to an `ast.Signal` or `ast.Method`.
"""
DIRECTION_IN = 'in'
DIRECTION_OUT = 'out'
required_attributes = ['type']
optional_attributes = ['direction', 'name']
# pylint: disable=too-many-arguments
def __init__(self, name, direction, type_, annotations=None, log=None):
"""
Construct a new ast.Argument.
Args:
name: argument name; may be empty
direction: DIRECTION_IN or DIRECTION_OUT
type_: type string for the argument; see http://goo.gl/uCpa5A
annotations: potentially empty dict of annotations applied to the
argument, mapping annotation name to an ast.Annotation
instance
log: subclass of `Log`, used to store log messages; can be None
"""
super(Argument, self).__init__(name, annotations, log)
type_parser = TypeParser(type_)
self.type = type_parser.parse()
if self.type is None:
message = type_parser.get_output()[0][3]
self.log.log_issue('argument-type',
'Error when parsing type ‘%s’ for argument '
'‘%s’: %s' %
(type_, name, message))
self.direction = direction or Argument.DIRECTION_IN
self._index = -1
@property
def pretty_name(self):
"""Format the argument's name as a human-readable string"""
if self.index == -1 and self.name is None:
res = 'unnamed'
elif self.index == -1:
res = '‘%s’' % self.name
elif self.name is None:
res = '%u' % self.index
else:
res = '%u (‘%s’)' % (self.index, self.name)
if self.parent:
parent_type = type(self.parent).__name__.lower()
res += ' of %s ‘%s’' % (parent_type, self.parent.pretty_name)
return res
@property
def index(self):
"""The index of this argument in its parent's list of arguments"""
# Slight optimization, assumes arguments cannot be reparented
if self._index != -1:
return self._index
if not self.parent:
return -1
else:
self._index = self.parent.arguments.index(self)
return self._index
class Annotation(BaseNode):
"""
AST representation of an element.
This represents an arbitrary key-value metadata annotation attached to one
of the nodes in an interface.
The annotation name can be one of the well-known ones described at
http://goo.gl/LgmNUe, or could be something else.
"""
optional_attributes = ['value']
def __init__(self, name, value=None, log=None):
"""
Construct a new ast.Annotation.
Args:
name: annotation name; a non-empty string
value: annotation value; any string is permitted
log: subclass of `Log`, used to store log messages; can be None
"""
super(Annotation, self).__init__(name, log=log)
self.value = value
@property
def pretty_name(self):
"""Format the annotation's name as a human-readable string"""
if not self.parent:
return self.name
return '%s of ‘%s’' % (self.name, self.parent.pretty_name)
dbus-deviation-0.6.0/dbusapi/tests/ 0000775 0001750 0001750 00000000000 13035747236 020033 5 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/dbusapi/tests/test_typeformatter.py 0000664 0001750 0001750 00000011103 13035744374 024345 0 ustar philip philip 0000000 0000000 #!/usr/bin/python
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4
#
# Copyright © 2017 Philip Withnall
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
Unit tests for dbusapi.typeformatter
"""
from dbusapi.typeformatter import BasicTypeFormatter
from dbusapi import types
import unittest
class TestBasicFormatterNormal(unittest.TestCase):
"""Test normal formatting of types with the BasicTypeFormatter."""
# pylint: disable=invalid-name
def assertFormat(self, type_instance, formatted): # noqa
formatter = BasicTypeFormatter()
self.assertEqual(formatter.format(type_instance), formatted)
def test_basic_types(self):
basic_type_map = {
types.Byte: 'Byte',
types.Boolean: 'Boolean',
types.Int16: 'Int16',
types.UInt16: 'UInt16',
types.Int32: 'Int32',
types.UInt32: 'UInt32',
types.Int64: 'Int64',
types.UInt64: 'UInt64',
types.Double: 'Double',
types.String: 'String',
types.ObjectPath: 'Object Path',
types.Signature: 'Signature',
types.Variant: 'Variant',
types.UnixFD: 'Unix FD',
}
for k, v in basic_type_map.items():
self.assertFormat(k(), v)
def test_array(self):
type_instance = types.Array()
type_instance.members.append(types.Byte())
self.assertFormat(type_instance, 'Array of [Byte]')
def test_struct(self):
type_instance = types.Struct()
type_instance.members.append(types.String())
type_instance.members.append(types.String())
type_instance.members.append(types.UInt16())
self.assertFormat(type_instance, 'Struct of (String, String, UInt16)')
def test_dict(self):
dict_instance = types.DictEntry()
dict_instance.members.append(types.String())
dict_instance.members.append(types.Variant())
array_instance = types.Array()
array_instance.members.append(dict_instance)
self.assertFormat(array_instance, 'Dict of {String: Variant}')
def test_invalid_dict(self):
"""Test that a bare DictEntry (not in an Array) is not formatted"""
dict_instance = types.DictEntry()
dict_instance.members.append(types.String())
dict_instance.members.append(types.Variant())
formatter = BasicTypeFormatter()
with self.assertRaises(AssertionError):
formatter.format(dict_instance)
def test_something_long(self):
type_signature = types.TypeSignature()
type_signature.members.append(types.Int32())
type_signature.members.append(types.Int32())
type_signature.members.append(types.Double())
type_signature.members.append(types.Double())
type_signature.members.append(types.Double())
struct = types.Struct()
struct.members.append(types.Int32())
struct.members.append(types.Double())
struct.members.append(types.Double())
type_signature.members.append(struct)
self.assertFormat(type_signature,
'Int32, Int32, Double, Double, Double, '
'Struct of (Int32, Double, Double)')
def test_nested_structs(self):
s1 = types.Struct()
s1.members.append(types.Int32())
s1.members.append(types.Int32())
s2 = types.Struct()
s2.members.append(types.UInt32())
s2.members.append(types.UInt32())
s3 = types.Struct()
s3.members.append(s1)
s3.members.append(s2)
self.assertFormat(s3, 'Struct of ('
'Struct of (Int32, Int32), '
'Struct of (UInt32, UInt32))')
def test_type_signature(self):
signature = types.TypeSignature()
signature.members.append(types.String())
signature.members.append(types.UInt64())
self.assertFormat(signature, 'String, UInt64')
if __name__ == '__main__':
# Run test suite
unittest.main()
dbus-deviation-0.6.0/dbusapi/tests/test_typeparser.py 0000664 0001750 0001750 00000013547 13035123314 023635 0 ustar philip philip 0000000 0000000 #!/usr/bin/python
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4
#
# Copyright © 2016 Kaloyan Tenchov
# Copyright © 2017 Philip Withnall
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
Unit tests for dbusapi.typeparser
"""
from dbusapi.typeparser import TypeParser
import unittest
def _test_parser(signature):
"""Build a TypeParser for a signature and parse it."""
parser = TypeParser(signature)
types = parser.parse()
return parser, types
class TestParserErrors(unittest.TestCase):
"""Test error handling in the TypeParser."""
# pylint: disable=invalid-name
def assertOutput(self, signature, partial_output): # noqa
(parser, types) = _test_parser(signature)
self.assertEqual(types, None)
actual_output = \
[(None, 'types', i[0], i[1]) for i in partial_output]
self.assertEqual(parser.get_output(), actual_output)
def test_reserved(self):
"""
Type code 114 'r' is reserved for use in bindings and implementations
to represent the general concept of a struct, and must not appear in
signatures used on D-Bus.
Similarly for the other type strings here.
https://dbus.freedesktop.org/doc/dbus-specification.html#idm399
"""
vectors = [
'r', 'e', 'm', '*', '?', '@', '&', '^'
]
for type_string in vectors:
# Unfortunately we can’t use TestCase.subTest here until we depend
# on Python 3.4 at a minimum.
self.assertOutput(type_string, [
('reserved-type',
'Reserved type ‘%s’ must not be used in signatures on '
'D-Bus.' % type_string),
])
def test_incomplete(self):
self.assertOutput(
"aa", [
('invalid-type', 'Incomplete array declaration.'),
])
def test_incomplete2(self):
self.assertOutput(
"(ii", [
('invalid-type', 'Incomplete structure declaration.'),
])
def test_incomplete3(self):
self.assertOutput(
"ii)", [
('unknown-type', 'Unknown type ‘)’.'),
])
def test_incomplete4(self):
self.assertOutput(
"a{suu}", [
('invalid-type', 'Invalid dictionary declaration.'),
])
def test_incomplete5(self):
self.assertOutput(
"a{su", [
('invalid-type', 'Incomplete dictionary declaration.'),
])
def test_incomplete6(self):
self.assertOutput(
"a{s", [
('invalid-type', 'Incomplete dictionary declaration.'),
])
def test_invalid_array(self):
self.assertOutput(
'a!', [
('unknown-type', 'Unknown type ‘!’.'),
]
)
def test_empty(self):
self.assertOutput(
"", [
('invalid-type', 'Empty type string.'),
]
)
def test_empty_tuple(self):
self.assertOutput(
'()', [
('invalid-type', 'Incomplete structure declaration.'),
]
)
def test_invalid_tuple(self):
self.assertOutput(
'(!', [
('unknown-type', 'Unknown type ‘!’.'),
]
)
def test_empty_dict(self):
self.assertOutput(
'{}', [
('invalid-type', 'Incomplete dictionary declaration.'),
]
)
def test_invalid_dict(self):
self.assertOutput(
'{!', [
('unknown-type', 'Unknown type ‘!’.'),
]
)
def test_underfilled_dict(self):
self.assertOutput(
'{s}', [
('invalid-type', 'Incomplete dictionary declaration.'),
]
)
def test_overfilled_dict(self):
self.assertOutput(
'{sss}', [
('invalid-type', 'Invalid dictionary declaration.'),
]
)
class TestParserNormal(unittest.TestCase):
"""Test normal parsing of unusual input in the TypeParser."""
# pylint: disable=invalid-name
def assertParse(self, signature): # noqa
(parser, type_signature) = _test_parser(signature)
self.assertEqual(parser.get_output(), [])
actual_signature = str(type_signature)
self.assertEqual(signature, actual_signature)
def test_parse_valid(self):
vectors = [
'y', 'b', 'n', 'q', 'i', 'ii', 'aiai', 'u', 'uu', 'x', 't', 'd',
's', 'o', 'g', 'v', 'h', 'ai', 'aai', 'au', 'av', '(iii)',
'(ii)(ii)', '(i(ii))', '(v)', '(ius)', 'a(ii)', 'a{us}', 'a{us}i'
]
for type_string in vectors:
# Unfortunately we can’t use TestCase.subTest here until we depend
# on Python 3.4 at a minimum.
self.assertParse(type_string)
class TestParserOutputCodes(unittest.TestCase):
"""Test the output codes from TypeParser."""
def test_unique(self):
codes = TypeParser.get_output_codes()
self.assertEqual(len(codes), len(set(codes)))
def test_non_empty(self):
codes = TypeParser.get_output_codes()
self.assertNotEqual(codes, [])
if __name__ == '__main__':
# Run test suite
unittest.main()
dbus-deviation-0.6.0/dbusapi/tests/test_interfaceparser.py 0000775 0001750 0001750 00000062153 13035123314 024614 0 ustar philip philip 0000000 0000000 #!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright © 2015 Collabora Ltd.
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
Unit tests for dbusapi.interfaceparser
"""
# pylint: disable=missing-docstring
from dbusapi.interfaceparser import InterfaceParser
import os
import tempfile
import unittest
def _create_temp_xml_file(xml):
"""Create a temporary XML file with the given contents."""
tmp_fd, tmp_name = tempfile.mkstemp(suffix='.xml', text=True)
xml_fd = os.fdopen(tmp_fd, 'wt')
xml_fd.write(xml)
xml_fd.close()
return tmp_name
def _test_parser_with_nodes(xml):
"""Build an InterfaceParser for the XML snippet and parse it."""
tmpfile = _create_temp_xml_file(xml)
parser = InterfaceParser(tmpfile)
root_node = parser.parse_with_nodes()
os.unlink(tmpfile)
return parser, root_node, tmpfile
def _test_parser(xml):
"""Build an InterfaceParser for the XML snippet and parse it."""
parser, root_node, tmpfile = _test_parser_with_nodes(xml)
interfaces = root_node.interfaces if root_node else None
return parser, interfaces, tmpfile
# pylint: disable=too-many-public-methods
class TestParserErrors(unittest.TestCase):
"""Test error handling in the InterfaceParser."""
# pylint: disable=invalid-name
def assertOutputWithNodes(self, xml, partial_output): # noqa
(parser, root_node, filename) = _test_parser_with_nodes(xml)
self.assertEqual(root_node, None)
actual_output = \
[(filename, 'parser', i[0], i[1]) for i in partial_output]
self.assertEqual(parser.get_output(), actual_output)
# pylint: disable=invalid-name
def assertOutput(self, xml, partial_output): # noqa
(parser, interfaces, filename) = _test_parser(xml)
self.assertEqual(interfaces, None)
actual_output = \
[(filename, 'parser', i[0], i[1]) for i in partial_output]
self.assertEqual(parser.get_output(), actual_output)
def test_unknown_root_node(self):
self.assertOutput(
" ", [
('unknown-node', 'Unknown root node ‘notnode’.'),
])
def test_nonabsolute_node_name(self):
self.assertOutputWithNodes(
" ", [
('node-name',
'Root node name is not an absolute object path ‘rel/N’.'),
])
def test_invalid_node_name(self):
self.assertOutputWithNodes(
" ", [
('node-name',
'Root node name is not an absolute object path ‘//’.'),
])
def test_missing_node_name(self):
self.assertOutputWithNodes(
" ", [
('missing-attribute',
'Missing required attribute ‘name’ in non-root node.'),
])
def test_nonrelative_node_name(self):
self.assertOutputWithNodes(
" ", [
('node-name',
'Non-root node name is not a relative object path ‘/abs/N’.'),
])
def test_duplicate_node(self):
self.assertOutputWithNodes(
" ", [
('duplicate-node',
'Duplicate node definition ‘N’.'),
])
def test_invalid_interface_name(self):
self.assertOutput(
" ", [
('interface-name',
'Invalid interface name ‘0’.'),
])
def test_duplicate_interface(self):
self.assertOutput(
" ", [
('duplicate-interface',
'Duplicate interface definition ‘I.I’.'),
])
def test_unknown_interface_node(self):
self.assertOutput(
" ", [
('unknown-node', 'Unknown node ‘badnode’ in root.'),
])
def test_interface_missing_name(self):
self.assertOutput(
" ", [
('missing-attribute',
'Missing required attribute ‘name’ in interface.'),
])
def test_invalid_method(self):
self.assertOutput(
""
" "
" ", [
('method-name',
'Invalid method name ‘0M’.'),
])
def test_duplicate_method(self):
self.assertOutput(
""
" "
" ", [
('duplicate-method',
'Duplicate method definition ‘I.I.M’.'),
])
def test_invalid_signal(self):
self.assertOutput(
""
" "
" ", [
('signal-name',
'Invalid signal name ‘*S’.'),
])
def test_duplicate_signal(self):
self.assertOutput(
""
" "
" ", [
('duplicate-signal',
'Duplicate signal definition ‘I.I.S’.'),
])
def test_invalid_signal_signature(self):
self.assertOutput(
""
" "
" ", [
('argument-type',
'Error when parsing type ‘?’ for argument ‘N’: '
'Reserved type ‘?’ must not be used in signatures on D-Bus.'),
])
def test_duplicate_property(self):
self.assertOutput(
""
" "
" "
" ", [
('duplicate-property',
'Duplicate property definition ‘I.I.P’.'),
])
def test_invalid_property_signature(self):
self.assertOutput(
""
" "
" ", [
('property-type',
'Error when parsing type ‘a?’ for property ‘P’: '
'Reserved type ‘?’ must not be used in signatures on D-Bus.'),
])
def test_unknown_sub_interface_node(self):
self.assertOutput(
" ", [
('unknown-node', 'Unknown node ‘badnode’ in interface ‘I.I’.'),
])
def test_method_missing_name(self):
self.assertOutput(
" ", [
('missing-attribute',
'Missing required attribute ‘name’ in method.'),
])
def test_unknown_method_node(self):
self.assertOutput(
""
" "
" ", [
('unknown-node', 'Unknown node ‘badnode’ in method ‘I.I.M’.'),
])
def test_signal_missing_name(self):
self.assertOutput(
" ", [
('missing-attribute',
'Missing required attribute ‘name’ in signal.'),
])
def test_unknown_signal_node(self):
self.assertOutput(
""
" "
" ", [
('unknown-node', 'Unknown node ‘badnode’ in signal ‘I.I.S’.'),
])
def test_property_missing_name(self):
self.assertOutput(
""
" "
" ", [
('missing-attribute',
'Missing required attribute ‘name’ in property.'),
])
def test_property_missing_type(self):
self.assertOutput(
""
" "
" ", [
('missing-attribute',
'Missing required attribute ‘type’ in property.'),
])
def test_property_missing_access(self):
self.assertOutput(
""
" "
" ", [
('missing-attribute',
'Missing required attribute ‘access’ in property.'),
])
def test_unknown_property_node(self):
self.assertOutput(
""
""
" "
" "
" ", [
('unknown-node',
'Unknown node ‘badnode’ in property ‘I.I.P’.'),
])
def test_unknown_arg_node(self):
self.assertOutput(
""
" "
" ", [
('unknown-node',
'Unknown node ‘badnode’ in argument ‘0 of method ‘I.I.M’’.'),
])
def test_arg_missing_type(self):
self.assertOutput(
""
" "
" ", [
('missing-attribute',
'Missing required attribute ‘type’ in arg.'),
])
def test_annotation_missing_name(self):
self.assertOutput(
""
" "
" ", [
('missing-attribute',
'Missing required attribute ‘name’ in annotation.'),
])
def test_unknown_annotation_node(self):
self.assertOutput(
""
" "
" ", [
('unknown-node',
'Unknown node ‘badnode’ in annotation ‘N of ‘I.I’’.'),
])
# pylint: disable=too-many-public-methods
class TestParserNormal(unittest.TestCase):
"""Test normal parsing of unusual input in the InterfaceParser."""
# pylint: disable=invalid-name
def assertParse(self, xml): # noqa
(parser, interfaces, _) = _test_parser(xml)
self.assertEqual(parser.get_output(), [])
return interfaces
def test_tp_spec_root(self):
"""Test that specifications wrapped in tp:spec are parsed."""
self.assertParse(
" ")
def test_ignored_namespaced_tags_interface(self):
self.assertParse(
""
" ")
def test_ignored_namespaced_tags(self):
self.assertParse(
""
""
""
" "
" ")
def test_doc_root(self):
"""Test that doc tags are ignored in the root."""
self.assertParse(
""
"Ignore me. "
"Ignore me. "
" ")
def test_doc_interface(self):
"""Test that doc tags are ignored in interfaces."""
self.assertParse(
""
"Ignore me. "
"Ignore me. "
" ")
def test_doc_method(self):
"""Test that doc tags are ignored in methods."""
self.assertParse(
""
"Ignore me. "
"Ignore me. "
" ")
def test_doc_signal(self):
"""Test that doc tags are ignored in signals."""
self.assertParse(
""
"Ignore me. "
"Ignore me. "
" ")
def test_doc_property(self):
"""Test that doc tags are ignored in properties."""
self.assertParse(
""
""
"Ignore me. "
"Ignore me. "
" ")
def test_doc_arg(self):
"""Test that doc tags are ignored in args."""
self.assertParse(
""
"Ignore me. "
"Ignore me. "
" ")
def test_doc_annotation(self):
"""Test that doc tags are ignored in annotations."""
self.assertParse(
""
"Ignore me. "
"Ignore me. "
" ")
def test_doc_comments(self):
"""Test that xml comments are *not* ignored"""
xml = (""
""
""
""
""
""
" "
" "
" ")
(parser, interfaces, _) = _test_parser(xml)
interface = interfaces.get('I.I')
self.assertIsNotNone(interface)
self.assertEquals(interface.comment, "Please consider me")
meth = interface.methods.get('foo')
self.assertIsNotNone(meth)
self.assertEquals(meth.comment, "Notice me too")
self.assertEquals(len(meth.arguments), 1)
arg = meth.arguments[0]
self.assertEquals(arg.comment, "And me!")
def test_line_numbers(self):
"""Test that line numbers are correctly computed"""
xml = ("\n"
"\n"
"\n"
"\n"
"\n"
"\n"
" \n"
" \n"
" \n"
" \n")
(parser, interfaces, _) = _test_parser(xml)
interface = interfaces.get('I.I')
self.assertIsNotNone(interface)
self.assertEqual(interface.line_number, 5)
self.assertEqual(interface.comment_line_number, 2)
meth = interface.methods.get('foo')
self.assertIsNotNone(meth)
self.assertEqual(meth.line_number, 9)
self.assertEqual(meth.comment_line_number, 6)
self.assertEquals(len(meth.arguments), 2)
arg = meth.arguments[0]
self.assertEqual(arg.line_number, 13)
self.assertEqual(arg.comment_line_number, 10)
arg = meth.arguments[1]
self.assertEqual(arg.line_number, 14)
self.assertEqual(arg.comment_line_number, -1)
def test_doc_annotations(self):
xml = (""
""
" "
" ")
(parser, interfaces, _) = _test_parser(xml)
interface = interfaces.get('I.I')
self.assertIsNotNone(interface)
self.assertEquals(interface.comment, "bla")
def test_multiline_comments(self):
xml = (""
""
""
" ")
(parser, interfaces, _) = _test_parser(xml)
interface = interfaces.get('I.I')
self.assertIsNotNone(interface)
self.assertEquals(interface.comment,
" Please consider that\n"
" multiline comment")
def test_ignored_comments(self):
xml = (""
""
""
" "
""
" ")
(parser, interfaces, _) = _test_parser(xml)
interface = interfaces.get('I.I')
self.assertIsNotNone(interface)
self.assertIsNone(interface.comment)
def test_root_node(self):
self.assertParse(""" """)
def test_dbus_spec_example(self):
"""
Test parsing the example from the D-Bus Specification:
http://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format
"""
self.assertParse("""
""")
class TestParserOutputCodes(unittest.TestCase):
"""Test the output codes from InterfaceParser."""
def test_unique(self):
codes = InterfaceParser.get_output_codes()
self.assertEqual(len(codes), len(set(codes)))
def test_non_empty(self):
codes = InterfaceParser.get_output_codes()
self.assertNotEqual(codes, [])
# pylint: disable=too-many-public-methods
class TestParserRecovery(unittest.TestCase):
"""
Test recovery from parser errors in InterfaceParser to detect subsequent
errors.
"""
# pylint: disable=invalid-name
def assertOutput(self, xml, partial_output): # noqa
(parser, interfaces, filename) = _test_parser(xml)
self.assertEqual(interfaces, None)
actual_output = \
[(filename, 'parser', i[0], i[1]) for i in partial_output]
self.assertEqual(parser.get_output(), actual_output)
return interfaces
def test_annotation_interface(self):
"""Test recovery from invalid annotations in an interface."""
self.assertOutput(
""
" "
" ", [
('missing-attribute',
'Missing required attribute ‘name’ in annotation.'),
('missing-attribute',
'Missing required attribute ‘name’ in method.'),
])
def test_annotation_method(self):
"""Test recovery from invalid annotations in a method."""
self.assertOutput(
""
" "
" ", [
('missing-attribute',
'Missing required attribute ‘name’ in annotation.'),
('missing-attribute',
'Missing required attribute ‘type’ in arg.'),
])
def test_annotation_signal(self):
"""Test recovery from invalid annotations in a signal."""
self.assertOutput(
""
" "
" ", [
('missing-attribute',
'Missing required attribute ‘name’ in annotation.'),
('missing-attribute',
'Missing required attribute ‘type’ in arg.'),
])
def test_annotation_property(self):
"""Test recovery from invalid annotations in a property."""
self.assertOutput(
""
""
" "
" "
" ", [
('missing-attribute',
'Missing required attribute ‘name’ in annotation.'),
('unknown-node',
'Unknown node ‘badnode’ in property ‘I.I.P’.'),
])
def test_annotation_arg(self):
"""Test recovery from invalid annotations in an arg."""
self.assertOutput(
""
" "
" ", [
('missing-attribute',
'Missing required attribute ‘name’ in annotation.'),
('unknown-node',
'Unknown node ‘badnode’ in argument ‘0 of method ‘I.I.M’’.'),
])
if __name__ == '__main__':
# Run test suite
unittest.main()
dbus-deviation-0.6.0/dbusapi/tests/__init__.py 0000664 0001750 0001750 00000000000 13026466535 022132 0 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/dbusapi/tests/test_ast.py 0000775 0001750 0001750 00000022253 13035123314 022223 0 ustar philip philip 0000000 0000000 #!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright © 2015 Collabora Ltd.
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
Unit tests for dbusapi.ast
"""
# pylint: disable=missing-docstring
from dbusapi import ast
from dbusapi.types import TypeSignature
import unittest
# pylint: disable=too-many-public-methods
class TestAstNames(unittest.TestCase):
"""Test AST node name generation."""
def test_interface(self):
iface = ast.Interface('Some.Interface')
self.assertEqual(iface.format_name(), 'Some.Interface')
def test_property(self):
prop = ast.Property('AProperty', 's', ast.Property.ACCESS_READ)
ast.Interface('Some.Interface', {}, {
'AProperty': prop,
})
self.assertEqual(prop.format_name(), 'Some.Interface.AProperty')
def test_property_unparented(self):
prop = ast.Property('AProperty', 's', ast.Property.ACCESS_READ)
self.assertEqual(prop.format_name(), 'AProperty')
def test_method(self):
method = ast.Method('AMethod', [])
ast.Interface('Some.Interface', {
'AMethod': method,
})
self.assertEqual(method.format_name(), 'Some.Interface.AMethod')
def test_method_unparented(self):
method = ast.Method('AMethod', [])
self.assertEqual(method.format_name(), 'AMethod')
def test_signal(self):
signal = ast.Signal('SomeSignal', [])
ast.Interface('Some.Interface', {}, {}, {
'SomeSignal': signal,
})
self.assertEqual(signal.format_name(), 'Some.Interface.SomeSignal')
def test_signal_unparented(self):
signal = ast.Signal('SomeSignal', [])
self.assertEqual(signal.format_name(), 'SomeSignal')
def test_argument(self):
arg = ast.Argument('self', ast.Argument.DIRECTION_IN, 's')
ast.Method('ParentMethod', [arg])
self.assertEqual(arg.format_name(),
'0 (‘self’) of method ‘ParentMethod’')
def test_argument_unparented(self):
arg = ast.Argument('self', ast.Argument.DIRECTION_IN, 's')
self.assertEqual(arg.format_name(), '‘self’')
def test_argument_unnamed(self):
arg = ast.Argument(None, ast.Argument.DIRECTION_IN, 's')
ast.Method('ParentMethod', [arg])
self.assertEqual(arg.format_name(), '0 of method ‘ParentMethod’')
# pylint: disable=invalid-name
def test_argument_unnamed_unparented(self):
arg = ast.Argument(None, ast.Argument.DIRECTION_IN, 's')
self.assertEqual(arg.format_name(), 'unnamed')
def test_annotation_interface(self):
annotation = ast.Annotation('SomeAnnotation', 'value')
ast.Interface('Some.Interface', {}, {}, {}, {
'SomeAnnotation': annotation,
})
self.assertEqual(annotation.format_name(),
'SomeAnnotation of ‘Some.Interface’')
def test_annotation_property(self):
annotation = ast.Annotation('SomeAnnotation', 'value')
prop = ast.Property('AProperty', 's', ast.Property.ACCESS_READ, {
'SomeAnnotation': annotation,
})
ast.Interface('Some.Interface', {}, {
'AProperty': prop,
})
self.assertEqual(annotation.format_name(),
'SomeAnnotation of ‘Some.Interface.AProperty’')
def test_annotation_method(self):
annotation = ast.Annotation('SomeAnnotation', 'value')
method = ast.Method('AMethod', [], {
'SomeAnnotation': annotation,
})
ast.Interface('Some.Interface', {
'AMethod': method,
})
self.assertEqual(annotation.format_name(),
'SomeAnnotation of ‘Some.Interface.AMethod’')
def test_annotation_signal(self):
annotation = ast.Annotation('SomeAnnotation', 'value')
signal = ast.Signal('ASignal', [], {
'SomeAnnotation': annotation,
})
ast.Interface('Some.Interface', {}, {}, {
'ASignal': signal,
})
self.assertEqual(annotation.format_name(),
'SomeAnnotation of ‘Some.Interface.ASignal’')
def test_annotation_argument(self):
annotation = ast.Annotation('SomeAnnotation', 'value')
arg = ast.Argument('Argument', ast.Argument.DIRECTION_IN, 's', {
'SomeAnnotation': annotation,
})
method = ast.Method('AMethod', [arg])
ast.Interface('Some.Interface', {
'AMethod': method,
})
self.assertEqual(annotation.format_name(),
'SomeAnnotation of ‘0 (‘Argument’)'
' of method ‘Some.Interface.AMethod’’')
def test_annotation_unparented(self):
annotation = ast.Annotation('SomeAnnotation', 'value')
self.assertEqual(annotation.format_name(), 'SomeAnnotation')
# pylint: disable=too-many-public-methods
class TestAstParenting(unittest.TestCase):
"""Test AST node parenting."""
def test_property(self):
annotation = ast.Annotation('SomeAnnotation', 'value')
self.assertEqual(annotation.parent, None)
prop = ast.Property('AProperty', 's', ast.Property.ACCESS_READ, {
'SomeAnnotation': annotation,
})
self.assertEqual(prop.interface, None)
self.assertEqual(annotation.parent, prop)
iface = ast.Interface('Some.Interface', {}, {
'AProperty': prop,
})
self.assertEqual(prop.interface, iface)
def test_method(self):
annotation = ast.Annotation('SomeAnnotation', 'value')
self.assertEqual(annotation.parent, None)
method = ast.Method('AMethod', [], {
'SomeAnnotation': annotation,
})
self.assertEqual(method.interface, None)
self.assertEqual(annotation.parent, method)
iface = ast.Interface('Some.Interface', {
'AMethod': method,
})
self.assertEqual(method.interface, iface)
def test_signal(self):
annotation = ast.Annotation('SomeAnnotation', 'value')
self.assertEqual(annotation.parent, None)
signal = ast.Signal('ASignal', [], {
'SomeAnnotation': annotation,
})
self.assertEqual(signal.interface, None)
self.assertEqual(annotation.parent, signal)
iface = ast.Interface('Some.Interface', {}, {
'ASignal': signal,
})
self.assertEqual(signal.interface, iface)
def test_argument(self):
annotation = ast.Annotation('SomeAnnotation', 'value')
self.assertEqual(annotation.parent, None)
arg = ast.Argument('SomeArgument', ast.Argument.DIRECTION_IN, 's', {
'SomeAnnotation': annotation,
})
self.assertEqual(arg.parent, None)
self.assertEqual(arg.index, -1)
self.assertEqual(annotation.parent, arg)
method = ast.Method('AMethod', [arg])
self.assertEqual(arg.parent, method)
self.assertEqual(arg.index, 0)
class TestAstTraversal(unittest.TestCase):
"""Test AST traversal."""
def test_walk(self):
annotation = ast.Annotation('SomeAnnotation', 'value')
method = ast.Method('AMethod', [], {
'SomeAnnotation': annotation,
})
iface = ast.Interface('Some.Interface', {
'AMethod': method,
})
children = [node for node in iface.walk()]
self.assertEquals(len(children), 2)
self.assertEquals(children[0], method)
self.assertEquals(children[1], annotation)
class TestAstSignatures(unittest.TestCase):
"""Test AST node signature parsing."""
def test_property(self):
prop = ast.Property('AProperty', 's', ast.Property.ACCESS_READ)
self.assertIsInstance(prop.type, TypeSignature)
self.assertEqual(str(prop.type), 's')
def test_argument(self):
arg = ast.Argument('self', ast.Argument.DIRECTION_IN, 's')
self.assertIsInstance(arg.type, TypeSignature)
self.assertEqual(str(arg.type), 's')
class TestAstLogging(unittest.TestCase):
"""Test error handling in AST"""
def test_duplicate(self):
method = ast.Method('AMethod', [])
iface = ast.Interface('Some.Interface', {
'AMethod': method,
})
self.assertListEqual(iface.log.issues, [])
duplicate_method = ast.Method('AMethod', [])
iface.add_child(duplicate_method)
self.assertListEqual(
iface.log.issues,
[(None,
'ast',
'duplicate-method',
'Duplicate method definition ‘Some.Interface.AMethod’.')])
if __name__ == '__main__':
# Run test suite
unittest.main()
dbus-deviation-0.6.0/HACKING 0000664 0001750 0001750 00000001541 13026466535 016232 0 ustar philip philip 0000000 0000000 Formatting
==========
All code should follow PEP8.
Commit messages
===============
dbus-deviation does not use a ChangeLog; it is auto-generated from the git log
when packaging a release. Commit messages should use a short tag in the commit
summary to allow easy identification of which part of the code base they
touch.
Versioning
==========
dbus-deviation uses an even–odd/stable–unstable versioning policy, where odd
minor version numbers are unstable releases, released periodically (with
increasing micro version numbers) and leading to a stable release with the
next even minor version number. API breaks are allowed in micro releases with
an odd minor version number, but not in micro releases with an even minor
version number.
It is encouraged to make a new micro release of an odd minor series after each
large API or feature addition or break.
dbus-deviation-0.6.0/dbus_deviation.egg-info/ 0000775 0001750 0001750 00000000000 13035747236 021733 5 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/dbus_deviation.egg-info/requires.txt 0000664 0001750 0001750 00000000005 13035747235 024325 0 ustar philip philip 0000000 0000000 lxml
dbus-deviation-0.6.0/dbus_deviation.egg-info/top_level.txt 0000664 0001750 0001750 00000000026 13035747235 024462 0 ustar philip philip 0000000 0000000 dbusapi
dbusdeviation
dbus-deviation-0.6.0/dbus_deviation.egg-info/PKG-INFO 0000664 0001750 0001750 00000015515 13035747235 023036 0 ustar philip philip 0000000 0000000 Metadata-Version: 1.0
Name: dbus-deviation
Version: 0.6.0
Summary:
Parse D-Bus introspection XML and process it in various ways
Home-page: http://people.collabora.com/~pwith/dbus-deviation/
Author: Philip Withnall
Author-email: philip.withnall@collabora.co.uk
License: LGPLv2.1+
Description: dbus-deviation
==============
dbus-deviation is a project for parsing D-Bus introspection XML and processing
it in various ways. Its main tool is dbus-interface-diff, which calculates the
difference between two D-Bus APIs for the purpose of checking for API breaks.
This functionality is also available as a Python module, dbusdeviation.
A second Python module, dbusapi, is provided for parsing D-Bus introspection
XML to produce an AST representing a D-Bus interface.
dbus-deviation’s API is currently unstable and is likely to change wildly.
Using dbus-deviation
====================
dbus-deviation can be used as a utility program or as a Python library.
The utility programs:
• dbus-interface-diff:
Check for differences between two versions of the same D-Bus API and
print details of each difference. It can check for problems with forwards
and backwards compatibility, as well as general informational
differences.
Example:
dbus-interface-diff \
com.example.Interface1.xml \ # old version of the interface
com.example.Interface2.xml # new version of the interface
• dbus-interface-vcs-helper:
This is a helper program designed to be used by dbus-deviation.mk.
As a library, the core object is an InterfaceParser, allowing a D-Bus API to
be parsed and represented as an AST. See the API documentation for more
explanation and examples.
dbus-deviation.mk
-----------------
This is a Makefile snippet which should be copied into your project, added to
git, and the following two lines included in your top-level Makefile.am:
dbus_api_xml_files = list of D-Bus interface XML files
-include $(top_srcdir)/dbus-deviation.mk
Do not add it to EXTRA_DIST. It is designed to work from git checkouts only.
Then run:
make dbus-deviation-mk-install
to set up the API signature database. This assumes that your project defines
D-Bus interfaces in XML files, and does not generate them at runtime.
Finally, copy pre-push.hook to .git/hooks/pre-push and ensure it’s executable.
This script will automatically update the API signature database when a new
release tag is pushed to the git remote.
There is currently no streamlined support for projects which generate D-Bus
interfaces at runtime.
dbus-deviation.mk defines the following rules:
• dist-dbus-api-compatibility (a dependency of dist-hook)
• check-dbus-api-compatibility (a dependency of check-local)
• dbus-deviation-mk-install (never triggered automatically)
Dependencies
============
• argparse
• lxml
Development
===========
For fun, dbus-deviation uses the following services to do continuous
integration and gather build statistics:
• https://travis-ci.org/pwithnall/dbus-deviation
• https://landscape.io/github/pwithnall/dbus-deviation
• https://coveralls.io/r/pwithnall/dbus-deviation
• https://codecov.io/github/pwithnall/dbus-deviation
Licensing
=========
dbus-deviation is licensed under the LGPL version 2.1 (or, at your option, any
later version). See COPYING for more details.
dbus-deviation versions 0.4.0 and earlier were licensed under the choice of the
Academic Free License version 2.1, or the GNU General Public License version 2
(or, at your option, any later version). This is the same license as D-Bus
itself. Version 0.5.0 was relicensed to LGPLv2.1+ as it’s a more standard license
with less ambiguity about its implications.
Bugs
====
Bug reports and patches should be sent via GitHub or Gitlab:
https://github.com/pwithnall/dbus-deviation
https://gitlab.com/dbus-deviation/dbus-deviation
Contact
=======
Philip Withnall
http://people.collabora.com/~pwith/dbus-deviation/
Overview of changes from dbus-deviation 0.5.0 to dbus-deviation 0.6.0
=====================================================================
Major changes:
• Split out the logging API to allow for reuse
• Expose source line numbers in dbusapi.ast
• Add a D-Bus type parser, dbusapi.typeparser (thanks to Kaloyan Tenchov)
• Drop Python 3.3 support as Sphinx no longer supports it
• Add a D-Bus type formatter, dbusapi.typeformatter
Overview of changes from dbus-deviation 0.4.0 to dbus-deviation 0.5.0
=====================================================================
Major changes:
• Add support for elements (Kaloyan Tenchov)
• Add object path, interface name and callable name validation
(Kaloyan Tenchov)
• Relicense to LGPLv2.1+
Overview of changes from dbus-deviation 0.3.0 to dbus-deviation 0.4.0
=====================================================================
Major changes:
• Improve test coverage
• Port to lxml instead of xml.etree
• Support parsing comments and documentation nodes
Overview of changes from dbus-deviation 0.2.0 to dbus-deviation 0.3.0
=====================================================================
Major changes:
• Improve utility arguments and variables in dbus-deviation.mk and vcs-helper
Overview of changes from dbus-deviation 0.1.0 to dbus-deviation 0.2.0
=====================================================================
Major changes:
• Improve error reporting
• Improve test coverage
• Add Makefile snippet and helper program (vcs-helper) for use in other
projects
Initial release of dbus-deviation 0.1.0
=======================================
Major changes:
• Initial version of the project
Platform: UNKNOWN
dbus-deviation-0.6.0/dbus_deviation.egg-info/zip-safe 0000664 0001750 0001750 00000000001 13035744240 023353 0 ustar philip philip 0000000 0000000
dbus-deviation-0.6.0/dbus_deviation.egg-info/SOURCES.txt 0000664 0001750 0001750 00000007436 13035747236 023631 0 ustar philip philip 0000000 0000000 .gitignore
.landscape.yaml
.travis.yml
AUTHORS
COPYING
HACKING
NEWS
README
dbus-deviation.doap
dbus-deviation.mk
pre-push.hook
setup.cfg
setup.py
version.py
.be/version
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/settings
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/0b90dd22-dcf6-43e7-88a2-b72bdcef61c9/body
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/0b90dd22-dcf6-43e7-88a2-b72bdcef61c9/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/43ad8034-30c8-42f3-9c0d-61f13d9b8537/body
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/43ad8034-30c8-42f3-9c0d-61f13d9b8537/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/1ae388e0-0d45-4faf-8744-471c338140c3/body
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/1ae388e0-0d45-4faf-8744-471c338140c3/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/91b7a01a-4b27-4617-900f-fd1243852d04/body
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/91b7a01a-4b27-4617-900f-fd1243852d04/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/comments/11c90632-88a0-4999-bfa4-19fd5857291e/body
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/comments/11c90632-88a0-4999-bfa4-19fd5857291e/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/comments/4581dbb2-105c-42f8-8858-7b8a4071a058/body
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/comments/4581dbb2-105c-42f8-8858-7b8a4071a058/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/comments/44b1317e-bff8-477e-9d13-50071beb825b/body
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/comments/44b1317e-bff8-477e-9d13-50071beb825b/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/values
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/comments/2cedc3e0-2161-4efa-8440-0c7a79eafe41/body
.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/comments/2cedc3e0-2161-4efa-8440-0c7a79eafe41/values
dbus_deviation.egg-info/PKG-INFO
dbus_deviation.egg-info/SOURCES.txt
dbus_deviation.egg-info/dependency_links.txt
dbus_deviation.egg-info/entry_points.txt
dbus_deviation.egg-info/requires.txt
dbus_deviation.egg-info/top_level.txt
dbus_deviation.egg-info/zip-safe
dbusapi/__init__.py
dbusapi/ast.py
dbusapi/interfaceparser.py
dbusapi/log.py
dbusapi/typeformatter.py
dbusapi/typeparser.py
dbusapi/types.py
dbusapi/tests/__init__.py
dbusapi/tests/test_ast.py
dbusapi/tests/test_interfaceparser.py
dbusapi/tests/test_typeformatter.py
dbusapi/tests/test_typeparser.py
dbusdeviation/__init__.py
dbusdeviation/interfacecomparator.py
dbusdeviation/tests/__init__.py
dbusdeviation/tests/test_interfacecomparator.py
dbusdeviation/utilities/__init__.py
dbusdeviation/utilities/diff.py
dbusdeviation/utilities/vcs_helper.py
docs/conf.py
docs/dbusapi.rst
docs/dbusdeviation.rst
docs/dbusdeviation.utilities.rst
docs/index.rst
website/errors.html
website/index.html dbus-deviation-0.6.0/dbus_deviation.egg-info/dependency_links.txt 0000664 0001750 0001750 00000000001 13035747235 026000 0 ustar philip philip 0000000 0000000
dbus-deviation-0.6.0/dbus_deviation.egg-info/entry_points.txt 0000664 0001750 0001750 00000000217 13035747235 025230 0 ustar philip philip 0000000 0000000 [console_scripts]
dbus-interface-diff = dbusdeviation.utilities.diff:main
dbus-interface-vcs-helper = dbusdeviation.utilities.vcs_helper:main
dbus-deviation-0.6.0/docs/ 0000775 0001750 0001750 00000000000 13035747236 016172 5 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/docs/dbusapi.rst 0000664 0001750 0001750 00000001553 13035744274 020356 0 ustar philip philip 0000000 0000000 dbusapi package
===============
dbusapi.ast module
------------------
.. automodule:: dbusapi.ast
:members:
:undoc-members:
:show-inheritance:
dbusapi.interfaceparser module
------------------------------
.. automodule:: dbusapi.interfaceparser
:members:
:undoc-members:
:show-inheritance:
dbusapi.typeparser module
-------------------------
.. automodule:: dbusapi.typeparser
:members:
:undoc-members:
:show-inheritance:
dbusapi.types module
--------------------
.. automodule:: dbusapi.types
:members:
:undoc-members:
:show-inheritance:
dbusapi.typeformatter module
----------------------------
.. automodule:: dbusapi.typeformatter
:members:
:undoc-members:
:show-inheritance:
dbusapi.log module
------------------
.. automodule:: dbusapi.log
:members:
:undoc-members:
:show-inheritance:
dbus-deviation-0.6.0/docs/dbusdeviation.utilities.rst 0000664 0001750 0001750 00000000155 13026466535 023577 0 ustar philip philip 0000000 0000000 Command-line utilities
======================
dbus-interface-diff utility
---------------------------
TODO
dbus-deviation-0.6.0/docs/index.rst 0000664 0001750 0001750 00000000770 13026466535 020037 0 ustar philip philip 0000000 0000000 .. dbus-deviation documentation master file, created by
sphinx-quickstart on Wed Apr 29 10:22:21 2015.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to dbus-deviation’s documentation!
==========================================
Contents:
.. toctree::
:maxdepth: 4
dbusapi
dbusdeviation
dbusdeviation.utilities
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
dbus-deviation-0.6.0/docs/conf.py 0000664 0001750 0001750 00000024053 13026466535 017475 0 ustar philip philip 0000000 0000000 # -*- coding: utf-8 -*-
#
# dbus-deviation documentation build configuration file, created by
# sphinx-quickstart on Wed Apr 29 10:22:21 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.viewcode',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'overridden in setup.py'
author = u'Philip Withnall'
copyright = u'2015, Philip Withnall'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = 'overridden in setup.py'
# The full version, including alpha/beta/rc tags.
release = 'overridden in setup.py'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = project + 'doc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', project + '.tex', project + u' Documentation',
author, 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', project, project + u' Documentation',
[author], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', project, project + u' Documentation',
author, project, 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# -- Options for Epub output ----------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
epub_author = author
epub_publisher = author
epub_copyright = copyright
# The basename for the epub file. It defaults to the project name.
#epub_basename = project
# The HTML theme for the epub output. Since the default themes are not optimized
# for small screen space, using the same theme for HTML and epub output is
# usually not wise. This defaults to 'epub', a theme designed to save visual
# space.
#epub_theme = 'epub'
# The language of the text. It defaults to the language option
# or en if the language is not set.
#epub_language = ''
# The scheme of the identifier. Typical schemes are ISBN or URL.
#epub_scheme = ''
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#epub_identifier = ''
# A unique identification for the text.
#epub_uid = ''
# A tuple containing the cover image and cover page html template filenames.
#epub_cover = ()
# A sequence of (type, uri, title) tuples for the guide element of content.opf.
#epub_guide = ()
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_pre_files = []
# HTML files shat should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_post_files = []
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# The depth of the table of contents in toc.ncx.
#epub_tocdepth = 3
# Allow duplicate toc entries.
#epub_tocdup = True
# Choose between 'default' and 'includehidden'.
#epub_tocscope = 'default'
# Fix unsupported image types using the PIL.
#epub_fix_images = False
# Scale large images.
#epub_max_image_width = 0
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#epub_show_urls = 'inline'
# If false, no index is generated.
#epub_use_index = True
dbus-deviation-0.6.0/docs/dbusdeviation.rst 0000664 0001750 0001750 00000000353 13026466535 021565 0 ustar philip philip 0000000 0000000 dbusdeviation package
=====================
dbusdeviation.interfacecomparator module
----------------------------------------
.. automodule:: dbusdeviation.interfacecomparator
:members:
:undoc-members:
:show-inheritance:
dbus-deviation-0.6.0/PKG-INFO 0000664 0001750 0001750 00000015515 13035747236 016346 0 ustar philip philip 0000000 0000000 Metadata-Version: 1.0
Name: dbus-deviation
Version: 0.6.0
Summary:
Parse D-Bus introspection XML and process it in various ways
Home-page: http://people.collabora.com/~pwith/dbus-deviation/
Author: Philip Withnall
Author-email: philip.withnall@collabora.co.uk
License: LGPLv2.1+
Description: dbus-deviation
==============
dbus-deviation is a project for parsing D-Bus introspection XML and processing
it in various ways. Its main tool is dbus-interface-diff, which calculates the
difference between two D-Bus APIs for the purpose of checking for API breaks.
This functionality is also available as a Python module, dbusdeviation.
A second Python module, dbusapi, is provided for parsing D-Bus introspection
XML to produce an AST representing a D-Bus interface.
dbus-deviation’s API is currently unstable and is likely to change wildly.
Using dbus-deviation
====================
dbus-deviation can be used as a utility program or as a Python library.
The utility programs:
• dbus-interface-diff:
Check for differences between two versions of the same D-Bus API and
print details of each difference. It can check for problems with forwards
and backwards compatibility, as well as general informational
differences.
Example:
dbus-interface-diff \
com.example.Interface1.xml \ # old version of the interface
com.example.Interface2.xml # new version of the interface
• dbus-interface-vcs-helper:
This is a helper program designed to be used by dbus-deviation.mk.
As a library, the core object is an InterfaceParser, allowing a D-Bus API to
be parsed and represented as an AST. See the API documentation for more
explanation and examples.
dbus-deviation.mk
-----------------
This is a Makefile snippet which should be copied into your project, added to
git, and the following two lines included in your top-level Makefile.am:
dbus_api_xml_files = list of D-Bus interface XML files
-include $(top_srcdir)/dbus-deviation.mk
Do not add it to EXTRA_DIST. It is designed to work from git checkouts only.
Then run:
make dbus-deviation-mk-install
to set up the API signature database. This assumes that your project defines
D-Bus interfaces in XML files, and does not generate them at runtime.
Finally, copy pre-push.hook to .git/hooks/pre-push and ensure it’s executable.
This script will automatically update the API signature database when a new
release tag is pushed to the git remote.
There is currently no streamlined support for projects which generate D-Bus
interfaces at runtime.
dbus-deviation.mk defines the following rules:
• dist-dbus-api-compatibility (a dependency of dist-hook)
• check-dbus-api-compatibility (a dependency of check-local)
• dbus-deviation-mk-install (never triggered automatically)
Dependencies
============
• argparse
• lxml
Development
===========
For fun, dbus-deviation uses the following services to do continuous
integration and gather build statistics:
• https://travis-ci.org/pwithnall/dbus-deviation
• https://landscape.io/github/pwithnall/dbus-deviation
• https://coveralls.io/r/pwithnall/dbus-deviation
• https://codecov.io/github/pwithnall/dbus-deviation
Licensing
=========
dbus-deviation is licensed under the LGPL version 2.1 (or, at your option, any
later version). See COPYING for more details.
dbus-deviation versions 0.4.0 and earlier were licensed under the choice of the
Academic Free License version 2.1, or the GNU General Public License version 2
(or, at your option, any later version). This is the same license as D-Bus
itself. Version 0.5.0 was relicensed to LGPLv2.1+ as it’s a more standard license
with less ambiguity about its implications.
Bugs
====
Bug reports and patches should be sent via GitHub or Gitlab:
https://github.com/pwithnall/dbus-deviation
https://gitlab.com/dbus-deviation/dbus-deviation
Contact
=======
Philip Withnall
http://people.collabora.com/~pwith/dbus-deviation/
Overview of changes from dbus-deviation 0.5.0 to dbus-deviation 0.6.0
=====================================================================
Major changes:
• Split out the logging API to allow for reuse
• Expose source line numbers in dbusapi.ast
• Add a D-Bus type parser, dbusapi.typeparser (thanks to Kaloyan Tenchov)
• Drop Python 3.3 support as Sphinx no longer supports it
• Add a D-Bus type formatter, dbusapi.typeformatter
Overview of changes from dbus-deviation 0.4.0 to dbus-deviation 0.5.0
=====================================================================
Major changes:
• Add support for elements (Kaloyan Tenchov)
• Add object path, interface name and callable name validation
(Kaloyan Tenchov)
• Relicense to LGPLv2.1+
Overview of changes from dbus-deviation 0.3.0 to dbus-deviation 0.4.0
=====================================================================
Major changes:
• Improve test coverage
• Port to lxml instead of xml.etree
• Support parsing comments and documentation nodes
Overview of changes from dbus-deviation 0.2.0 to dbus-deviation 0.3.0
=====================================================================
Major changes:
• Improve utility arguments and variables in dbus-deviation.mk and vcs-helper
Overview of changes from dbus-deviation 0.1.0 to dbus-deviation 0.2.0
=====================================================================
Major changes:
• Improve error reporting
• Improve test coverage
• Add Makefile snippet and helper program (vcs-helper) for use in other
projects
Initial release of dbus-deviation 0.1.0
=======================================
Major changes:
• Initial version of the project
Platform: UNKNOWN
dbus-deviation-0.6.0/dbusdeviation/ 0000775 0001750 0001750 00000000000 13035747236 020102 5 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/dbusdeviation/interfacecomparator.py 0000664 0001750 0001750 00000055477 13026466535 024526 0 ustar philip philip 0000000 0000000 # -*- coding: utf-8 -*-
#
# Copyright © 2015 Collabora Ltd.
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
Module providing a `InterfaceComparator` object for comparing two D-Bus APIs
(provided as abstract syntax trees from the introspection XML), to determine if
they differ in API-incompatible ways.
"""
from dbusapi import ast
# Warning categories.
WARNING_CATEGORIES = [
'info',
'backwards-compatibility',
'forwards-compatibility',
]
class InterfaceComparator(object):
"""
Compare two D-Bus interface descriptions and determine how they differ.
Differences are given different severity levels, depending on whether they
affect
* nothing, and are purely decorative; for example, changing the name of a
method argument
* forwards compatibility, where code written against the new interface
may not work against the old interface; for example, because it uses a
newly added method
* backwards compatibility, where code written against the old interface
may not work against the new interface; for example, because it changes
the type of a property
"""
# Output severity levels.
OUTPUT_INFO = 'info'
OUTPUT_FORWARDS_INCOMPATIBLE = 'forwards-compatibility'
OUTPUT_BACKWARDS_INCOMPATIBLE = 'backwards-compatibility'
def __init__(self, old_interfaces, new_interfaces,
enabled_warnings=None, disabled_warnings=None,
new_filename=None):
"""
Construct a new InterfaceComparator.
Args:
old_interfaces: non-empty dict of old interfaces, mapping
interface name to an ast.Interface instance
new_interfaces: non-empty dict of new interfaces, mapping
interface name to an ast.Interface instance
enabled_warnings: potentially empty list of warning categories and
codes to enable
disabled_warnings: potentially empty list of warning categories and
codes to disable
new_filename: path to the new D-Bus interface file,
or None if unknown
"""
self._old_interfaces = old_interfaces
self._new_interfaces = new_interfaces
self._new_filename = new_filename
self._output = []
if enabled_warnings is not None:
self._enabled_warnings = enabled_warnings
else:
self._enabled_warnings = WARNING_CATEGORIES
if disabled_warnings is not None:
self._disabled_warnings = disabled_warnings
else:
self._disabled_warnings = []
@staticmethod
def get_output_codes():
"""Return a list of all possible output codes."""
# FIXME: Hard-coded for the moment.
return [
'interface-added',
'interface-removed',
'deprecated',
'undeprecated'
'c-symbol-changed',
'reply-added',
'reply-removed',
'ecs-changed-true-invalidates',
'ecs-changed-true-false',
'ecs-changed-true-const',
'ecs-changed-invalidates-true',
'ecs-changed-invalidates-false',
'ecs-changed-invalidates-const',
'ecs-changed-false-invalidates',
'ecs-changed-false-true',
'ecs-changed-false-const',
'ecs-changed-const-invalidates',
'ecs-changed-const-true',
'ecs-changed-const-false',
'method-added',
'method-removed',
'property-added',
'property-removed',
'signal-added',
'signal-removed',
'argument-added',
'argument-removed',
'property-type-changed',
'property-access-changed-read-readwrite',
'property-access-changed-read-write',
'property-access-changed-write-read',
'property-access-changed-write-readwrite',
'property-access-changed-readwrite-read',
'property-access-changed-readwrite-write',
'argument-name-changed',
'argument-type-changed',
'argument-direction-changed-in-out',
'argument-direction-changed-out-in',
]
def _issue_output(self, level, code, message):
"""Append a message to the comparator output."""
self._output.append((self._new_filename, level, code, message))
def _warning_enabled(self, level, code):
"""Determine whether the given output level is enabled for output."""
return ((level in self._enabled_warnings and
level not in self._disabled_warnings and
code not in self._disabled_warnings) or
(code in self._enabled_warnings and
code not in self._disabled_warnings))
def get_output(self):
"""
Return all the log messages generated by the latest call to compare().
Disabled warnings will not be returned.
"""
out = []
for (filename, level, code, message) in self._output:
if not self._warning_enabled(level, code):
continue
out.append((filename, level, code, message))
return out
def compare(self):
"""
Compare the two interfaces and store the results.
Returns:
The list of relevant warnings to output; an empty list otherwise.
The return value is affected by the categories of enabled
warnings.
"""
self._output = []
for (name, interface) in self._old_interfaces.items():
# See if the old interface exists in the new file.
if name not in self._new_interfaces:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'interface-removed',
'Interface ‘%s’ has been removed.' % name)
else:
# Compare the two.
self._compare_interfaces(interface, self._new_interfaces[name])
for (name, interface) in self._new_interfaces.items():
# See if the new interface exists in the old file.
if name not in self._old_interfaces:
self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE,
'interface-added',
'Interface ‘%s’ has been added.' % name)
# Work out the exit status.
return self.get_output()
# pylint: disable=too-many-branches
def _compare_annotations(self, old_node, new_node): # noqa
"""Compare two ast.Annotation instances."""
def _get_string_annotation(node, annotation_name, default):
"""
Get an annotation value as a string.
Reference: http://goo.gl/3EtdNf
Returns:
The value of the `annotation_name` annotation as a string, or
`default` if no annotation exists by that name.
"""
if annotation_name in node.annotations:
return node.annotations[annotation_name].value
return default
def _get_bool_annotation(node, annotation_name, default):
"""
Get an annotation value as a boolean.
Reference: http://goo.gl/3EtdNf
Returns:
The value of the `annotation_name` annotation as a boolean, or
`default` if no annotation exists by that name.
"""
if annotation_name in node.annotations:
return node.annotations[annotation_name].value == 'true'
return default
def _get_ecs_annotation(node):
"""
Get the value of the EmitsChangedSignal annotation.
Reference: http://goo.gl/3EtdNf
Returns:
The value of the
`org.freedesktop.DBus.Property.EmitsChangedSignal` annotation,
if it exists, or the default, calculated as per the
specification.
"""
name = 'org.freedesktop.DBus.Property.EmitsChangedSignal'
if name in node.annotations:
return node.annotations[name].value
elif isinstance(node, ast.Property):
assert node.interface is not None
return _get_ecs_annotation(node.interface)
else:
return 'true'
old_deprecated = \
_get_bool_annotation(old_node,
'org.freedesktop.DBus.Deprecated', False)
new_deprecated = \
_get_bool_annotation(new_node,
'org.freedesktop.DBus.Deprecated', False)
if old_deprecated and not new_deprecated:
self._issue_output(self.OUTPUT_INFO, 'undeprecated',
'Node ‘%s’ has been un-deprecated.' %
old_node.format_name())
elif not old_deprecated and new_deprecated:
self._issue_output(self.OUTPUT_INFO, 'deprecated',
'Node ‘%s’ has been deprecated.' %
old_node.format_name())
old_c_symbol = \
_get_string_annotation(old_node,
'org.freedesktop.DBus.GLib.CSymbol', '')
new_c_symbol = \
_get_string_annotation(new_node,
'org.freedesktop.DBus.GLib.CSymbol', '')
if old_c_symbol != new_c_symbol:
self._issue_output(self.OUTPUT_INFO, 'c-symbol-changed',
'Node ‘%s’ has changed its C symbol from ‘%s’ '
'to ‘%s’.' %
(old_node.format_name(), old_c_symbol,
new_c_symbol))
old_no_reply = \
_get_bool_annotation(old_node,
'org.freedesktop.DBus.Method.NoReply', False)
new_no_reply = \
_get_bool_annotation(new_node,
'org.freedesktop.DBus.Method.NoReply', False)
if old_no_reply and not new_no_reply:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'reply-added',
'Node ‘%s’ has been marked as returning a '
'reply.' % old_node.format_name())
elif not old_no_reply and new_no_reply:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'reply-removed',
'Node ‘%s’ has been marked as not returning a '
'reply.' % old_node.format_name())
old_ecs = _get_ecs_annotation(old_node)
new_ecs = _get_ecs_annotation(new_node)
output_code = 'ecs-changed-%s-%s' % (old_ecs, new_ecs)
# New
# | true | invalidates | const | false
# | true | xxxx | B2 | F3 | F3
# Old | invalidates | B2 | xxxxxxxxxxx | F3 | F3
# | const | B1 | B1 | xxxxx | B4
# | false | B1 | B1 | F4 | xxxxx
#
# B = Backwards-compatible; F = Forwards-compatible
# 1 = Started notifying
# 2 = Property switched lists in PropertiesChanged
# 3 = Stopped notifying
# 4 = const semantics changed
if old_ecs in ['true', 'invalidates'] and \
new_ecs in ['false', 'const']:
self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE, output_code,
'Node ‘%s’ stopped emitting '
'org.freedesktop.DBus.Properties.'
'PropertiesChanged.' %
old_node.format_name())
elif (old_ecs in ['false', 'const'] and
new_ecs in ['true', 'invalidates']):
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, output_code,
'Node ‘%s’ started emitting '
'org.freedesktop.DBus.Properties.'
'PropertiesChanged.' %
old_node.format_name())
elif old_ecs == 'true' and new_ecs == 'invalidates':
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, output_code,
'Node ‘%s’ stopped emitting its new value in '
'org.freedesktop.DBus.Properties.'
'PropertiesChanged.' %
old_node.format_name())
elif old_ecs == 'invalidates' and new_ecs == 'true':
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, output_code,
'Node ‘%s’ started emitting its new value in '
'org.freedesktop.DBus.Properties.'
'PropertiesChanged.' %
old_node.format_name())
elif old_ecs == 'const' and new_ecs == 'false':
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, output_code,
'Node ‘%s’ stopped being a constant.' %
old_node.format_name())
elif old_ecs == 'false' and new_ecs == 'const':
self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE, output_code,
'Node ‘%s’ became a constant.' %
old_node.format_name())
# pylint: disable=too-many-branches
def _compare_interfaces(self, old_interface, new_interface): # noqa
"""Compare two ast.Interface instances."""
# Precondition of calling this method.
assert old_interface.name == new_interface.name
# Compare methods.
for (name, method) in old_interface.methods.items():
if name not in new_interface.methods:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'method-removed',
'Method ‘%s’ has been removed.' %
method.format_name())
else:
self._compare_methods(method, new_interface.methods[name])
for (name, method) in new_interface.methods.items():
if name not in old_interface.methods:
self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE,
'method-added',
'Method ‘%s’ has been added.' %
method.format_name())
# Compare properties
for (name, prop) in old_interface.properties.items():
if name not in new_interface.properties:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'property-removed',
'Property ‘%s’ has been removed.' %
prop.format_name())
else:
self._compare_properties(prop, new_interface.properties[name])
for (name, prop) in new_interface.properties.items():
if name not in old_interface.properties:
self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE,
'property-added',
'Property ‘%s’ has been added.' %
prop.format_name())
# Compare signals
for (name, signal) in old_interface.signals.items():
if name not in new_interface.signals:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'signal-removed',
'Signal ‘%s’ has been removed.' %
signal.format_name())
else:
self._compare_signals(signal,
new_interface.signals[name])
for (name, signal) in new_interface.signals.items():
if name not in old_interface.signals:
self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE,
'signal-added',
'Signal ‘%s’ has been added.' %
signal.format_name())
# Compare annotations
self._compare_annotations(old_interface, new_interface)
def _compare_methods(self, old_method, new_method):
"""Compare two ast.Method instances."""
# Precondition of calling this method.
assert old_method.name == new_method.name
# Compare the argument lists.
n_old_args = len(old_method.arguments)
n_new_args = len(new_method.arguments)
for i in range(max(n_old_args, n_new_args)):
if i >= n_old_args:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-added',
'Argument %s '
'has been added.' %
new_method.arguments[i].format_name())
elif i >= n_new_args:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-removed',
'Argument %s '
'has been removed.' %
old_method.arguments[i].format_name())
else:
self._compare_arguments(old_method.arguments[i],
new_method.arguments[i])
# Compare annotations
self._compare_annotations(old_method, new_method)
def _compare_properties(self, old_property, new_property):
"""Compare two ast.Property instances."""
# Precondition of calling this method.
assert old_property.name == new_property.name
if old_property.type != new_property.type:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'property-type-changed',
'Property ‘%s’ has changed type from ‘%s’ '
'to ‘%s’.' %
(old_property.format_name(),
old_property.type, new_property.type))
error_code = 'property-access-changed-%s-%s' % \
(old_property.access, new_property.access)
if (old_property.access == ast.Property.ACCESS_READ or
old_property.access == ast.Property.ACCESS_WRITE) and \
new_property.access == ast.Property.ACCESS_READWRITE:
# Property has become less restrictive.
self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE, error_code,
'Property ‘%s’ has changed access from '
'‘%s’ to ‘%s’, becoming less restrictive.' %
(old_property.format_name(),
old_property.access, new_property.access))
elif old_property.access != new_property.access:
# Access has changed incompatibly.
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, error_code,
'Property ‘%s’ has changed access from '
'‘%s’ to ‘%s’.' %
(old_property.format_name(),
old_property.access, new_property.access))
# Compare annotations
self._compare_annotations(old_property, new_property)
def _compare_signals(self, old_signal, new_signal):
"""Compare two ast.Signal instances."""
# Precondition of calling this method.
assert old_signal.name == new_signal.name
# Compare the argument lists.
n_old_args = len(old_signal.arguments)
n_new_args = len(new_signal.arguments)
for i in range(max(n_old_args, n_new_args)):
if i >= n_old_args:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-added',
'Argument %s '
'has been added.' %
new_signal.arguments[i].format_name())
elif i >= n_new_args:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-removed',
'Argument %s '
'has been removed.' %
old_signal.arguments[i].format_name())
else:
self._compare_arguments(old_signal.arguments[i],
new_signal.arguments[i])
# Compare annotations
self._compare_annotations(old_signal, new_signal)
def _compare_arguments(self, old_arg, new_arg):
"""Compare two ast.Argument instances."""
if old_arg.name != new_arg.name:
self._issue_output(self.OUTPUT_INFO,
'argument-name-changed',
'Argument %s has changed '
'name from ‘%s’ to ‘%s’.' %
(old_arg.pretty_name,
old_arg.name, new_arg.name))
if old_arg.type != new_arg.type:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-type-changed',
'Argument %s has changed '
'type from ‘%s’ to ‘%s’.' %
(old_arg.pretty_name,
old_arg.type, new_arg.type))
if old_arg.direction != new_arg.direction:
self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-direction-changed-%s-%s' %
(old_arg.direction, new_arg.direction),
'Argument %s has changed '
'direction from ‘%s’ to ‘%s’.' %
(old_arg.pretty_name,
old_arg.direction, new_arg.direction))
# Compare annotations
self._compare_annotations(old_arg, new_arg)
dbus-deviation-0.6.0/dbusdeviation/utilities/ 0000775 0001750 0001750 00000000000 13035747236 022115 5 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/dbusdeviation/utilities/diff.py 0000775 0001750 0001750 00000017670 13026466535 023415 0 ustar philip philip 0000000 0000000 #!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright © 2015, 2016 Collabora Ltd.
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
D-Bus interface comparator. This allows two D-Bus introspection XML files to be
compared for API compatibility, and any incompatibilities debugged.
Compatibility warnings are split into categories, separating forwards- and
backwards-compatibility in case one or other is not cared about by the
interface maintainer. See the documentation for InterfaceComparator for an
explanation of the two types of compatibility.
"""
import argparse
import os
import sys
from lxml import etree
from dbusapi.interfaceparser import InterfaceParser
from dbusdeviation.interfacecomparator import InterfaceComparator
# Warning categories.
WARNING_CATEGORIES = [
'info',
'backwards-compatibility',
'forwards-compatibility',
'parser',
]
def _parse_file(filename, parser):
"""
Parse an XML interface file.
Return a list of ast.Interfaces. If the file is empty, return an empty
list. Print an error and exit if parsing fails.
"""
try:
interfaces = parser.parse()
# Handle parse errors.
if interfaces is None:
_print_output(parser.get_output())
sys.exit(1)
return interfaces
# pylint: disable=E1101
except etree.XMLSyntaxError as err:
# If the file is empty, treat it as a non-existent Interface. This
# allows for diffs of added files.
if os.path.getsize(filename) == 0:
return {}
else:
sys.stderr.write('%s: error: %s\n' % (filename, err))
sys.exit(1)
def _format_level(level, enable_colour=True, justified_length=0):
"""Format a warning level as a human-readable string."""
colours = {
'info': '\033[96;1m',
'forwards-compatibility': '\033[35;1m',
'backwards-compatibility': '\033[91;1m',
'parser': '\033[91;1m',
}
formats = {
'info': 'note',
'forwards-compatibility': 'warn',
'backwards-compatibility': 'error',
'parser': 'error',
}
out = formats[level]
if justified_length > 0:
out = out.rjust(justified_length)
if enable_colour:
out = colours[level] + out + '\033[0m'
return out
def _get_fd_for_level(level):
"""Get the output file descriptor to use for the output level."""
if level == InterfaceComparator.OUTPUT_INFO:
return sys.stdout
return sys.stderr
def _print_output(output, include_uris=True, enable_colour=True):
"""
Print all the log messages generated by the latest call to compare().
The messages will be printed to stdout and/or stderr as appropriate.
"""
# Justify the error codes.
try:
max_code_len = max([len(o[2]) for o in output])
max_level_len = max([len(_format_level(o[1], False)) for o in output])
except ValueError:
max_code_len = 0
max_level_len = 0
for (filename, level, code, message) in output:
formatted_level = _format_level(level, enable_colour, max_level_len)
fd_for_level = _get_fd_for_level(level)
errors_page = 'https://people.collabora.com' \
'/~pwith/dbus-deviation/errors.html'
explanation_uri = '%s#%s' % (errors_page, code)
formatted_code = code.rjust(max_code_len)
if enable_colour:
formatted_code = '\033[1m%s\033[0m' % formatted_code
explanation_uri = '\033[90m%s\033[0m' % explanation_uri
else:
formatted_code = formatted_code
if filename is None:
output = '%s: %s: %s\n' % \
(formatted_level, formatted_code, message)
else:
output = '%s: %s: %s: %s\n' % \
(filename, formatted_level, formatted_code, message)
fd_for_level.write(output)
if include_uris:
fd_for_level.write(' %s\n' % explanation_uri)
def _calculate_exit_status(args, output):
"""
Build the exit status for the program.
This will be non-zero if any errors were outputted, or if --fatal-warnings
was passed and any warnings were outputted. Otherwise it will be zero.
"""
outputted_errors = False
outputted_warnings = False
for (_, level, _, _) in output:
if level in ['backwards-compatibility', 'parser']:
outputted_errors = True
elif level in ['forwards-compatibility']:
outputted_warnings = True
if outputted_errors or (args.fatal_warnings and outputted_warnings):
return len(output)
return 0
def main():
"""Main utility implementation."""
codes = InterfaceComparator.get_output_codes()
codes += InterfaceParser.get_output_codes()
# Parse command line arguments.
parser = argparse.ArgumentParser(
description='Comparing D-Bus interface definitions')
parser.add_argument('old_file', type=str, help='Old interface XML file')
parser.add_argument('new_file', type=str, help='New interface XML file')
parser.add_argument('--file-display-name', dest='file_display_name',
type=str,
help='Human-readable name for new interface XML file '
'(if --new-file is a pipe, for example)')
parser.add_argument('--fatal-warnings', action='store_const', const=True,
default=False, help='Treat all warnings as fatal')
parser.add_argument('--warnings', dest='warnings', metavar='CATEGORY,…',
type=str,
help='Warning categories (%s; %s)' %
(', '.join(WARNING_CATEGORIES),
', '.join(codes)))
args = parser.parse_args()
if not args.old_file or not args.new_file:
parser.print_help()
sys.exit(1)
if args.warnings is None or args.warnings == 'all':
# Enable all warnings by default
warnings_args = WARNING_CATEGORIES
elif args.warnings == 'none':
warnings_args = []
else:
warnings_args = args.warnings.split(',')
for warning_arg in warnings_args:
if warning_arg[:3] == 'no-':
warning_arg = warning_arg[3:]
if warning_arg not in WARNING_CATEGORIES and warning_arg not in codes:
sys.stderr.write('%s: Unrecognized warning ‘%s’.\n' %
(sys.argv[0], warning_arg))
parser.print_help()
sys.exit(1)
enabled_warnings = [arg for arg in warnings_args if arg[:3] != 'no-']
disabled_warnings = [arg[3:] for arg in warnings_args if arg[:3] == 'no-']
# Parse the two files.
old_parser = InterfaceParser(args.old_file)
new_parser = InterfaceParser(args.new_file)
old_interfaces = _parse_file(args.old_file, old_parser)
new_interfaces = _parse_file(args.new_file, new_parser)
# Work out the human-readable name of the new XML filename.
if args.file_display_name is not None:
new_filename = args.file_display_name
else:
new_filename = args.new_file
# Compare the interfaces.
comparator = InterfaceComparator(old_interfaces, new_interfaces,
enabled_warnings, disabled_warnings,
new_filename)
out = comparator.compare()
_print_output(out)
sys.exit(_calculate_exit_status(args, out))
if __name__ == '__main__':
main()
dbus-deviation-0.6.0/dbusdeviation/utilities/__init__.py 0000664 0001750 0001750 00000000000 13026466535 024214 0 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/dbusdeviation/utilities/vcs_helper.py 0000775 0001750 0001750 00000045624 13026466535 024637 0 ustar philip philip 0000000 0000000 #!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright © 2015 Collabora Ltd.
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
Wrapper around dbus-interface-diff to integrate it with a VCS
This implements an API signature database in the project’s version control
system so that all users of the VCS can do API compatibility between all past
signed releases. Currently, only git is supported.
Requirements:
• Support out-of-tree builds, where srcdir ≠ builddir.
• Support bare repositories, or where GIT_WORK_TREE ≠ .git.
• Exit with status 0 if GIT_DIR does not exist; support the use case of
running `make distcheck` inside an extracted release tarball.
• Support both tag-before-dist and tag-after-dist workflows.
"""
import argparse
from contextlib import contextmanager
import os
import pipes
import shlex
import shutil
import subprocess
import sys
import tempfile
@contextmanager
def named_pipe():
"""Create and cleanup a named pipe in a temporary directory."""
dirname = tempfile.mkdtemp()
try:
path = os.path.join(dirname, 'fifo')
os.mkfifo(path)
yield path
finally:
shutil.rmtree(dirname)
def _git_command(args, command):
"""Build a git command line with standard arguments."""
out = [args.git]
if args.git_dir != '':
out += ['--git-dir', args.git_dir]
if args.git_work_tree != '':
out += ['--work-tree', args.git_work_tree]
return out + [command]
def _format_command(args):
"""Local wrapper for pipes.quote() with support for Python 2.7."""
try:
# pylint: disable=no-member
return ' '.join(shlex.quote(a) for a in args)
except AttributeError:
try:
# pylint: disable=no-member
return ' '.join(pipes.quote(a) for a in args)
except AttributeError:
# Give up
return ' '.join(args)
def _get_contents_of_file(args, tag, api_xml_file):
"""Get the git object ID of api_xml_file in the tag revision."""
rev = subprocess.check_output(_git_command(args, 'rev-parse') +
['--verify', '--quiet',
'%s^{tag}:%s' % (tag, api_xml_file)])
return rev.strip().decode('utf-8')
def _set_notes_for_ref(args, tag, api_xml_basename, notes):
"""Store the notes object ID as api_xml_basename in the tag revision."""
with open(os.devnull, 'w') as dev_null:
subprocess.check_output(_git_command(args, 'notes') +
['--ref',
'refs/%s/%s' %
(args.dbus_api_git_refs, api_xml_basename),
'add', '-C', notes, tag],
stderr=dev_null)
def _notes_exist_for_ref(args, tag, api_xml_basename):
"""Check whether notes have already been stored for this file and tag."""
with open(os.devnull, 'w') as dev_null:
status = subprocess.call(_git_command(args, 'notes') +
['--ref',
'refs/%s/%s' %
(args.dbus_api_git_refs, api_xml_basename),
'show', tag],
stdout=dev_null,
stderr=dev_null)
return status == 0
def _get_notes_filename_for_head(args, api_xml_basename):
"""Get the filename of api_xml_basename in the git work tree."""
filename = subprocess.check_output(_git_command(args, 'ls-files') +
['--full-name',
'*/%s' % api_xml_basename])
filename = filename.strip().decode('utf-8')
# Resolve the relative path against the work tree.
if args.git_work_tree != '':
filename = os.path.join(args.git_work_tree, filename)
return filename
def _fetch_notes(args):
"""Fetch the latest API signature database from the remote."""
subprocess.check_output(_git_command(args, 'fetch') +
[args.git_remote_origin,
'refs/%s/*:refs/%s/*' %
(args.dbus_api_git_refs, args.dbus_api_git_refs)])
def _push_notes(args):
"""Push the local API signature database to the remote."""
command = _git_command(args, 'push')
command += [args.git_remote_origin,
'refs/' + args.dbus_api_git_refs + '/*']
if not args.no_push:
subprocess.check_output(command)
else:
sys.stdout.write('Run this command to push the API signature '
'database:\n'
' %s\n' % _format_command(command))
def _is_release(args, ref):
"""Check whether ref identifies a signed tag."""
with open(os.devnull, 'w') as dev_null:
code = subprocess.call(_git_command(args, 'rev-parse') +
['--verify', ref],
stdout=dev_null, stderr=dev_null)
return code == 0
def _get_latest_release(args):
"""Get the name of the latest signed tag."""
tag_list = subprocess.check_output(_git_command(args, 'rev-list') +
['--tags', '--max-count=1'])
tag_list = tag_list.strip().decode('utf-8').split('\n')
latest_tag = subprocess.check_output(_git_command(args, 'describe') +
['--tags'] + tag_list)
return latest_tag.strip().decode('utf-8')
def command_dist(args):
"""Store the current API signature against the latest signed tag."""
# Get the latest git tag
try:
latest_tag = _get_latest_release(args)
except subprocess.CalledProcessError:
sys.stderr.write('error: Failed to find latest git tag: %s.')
return 1
# Store notes for each API file
for api_xml_file in args.dbus_api_xml_files:
try:
api_xml_basename = os.path.basename(api_xml_file)
# Do notes already exist for this file and tag?
if args.ignore_existing and \
_notes_exist_for_ref(args, latest_tag, api_xml_basename):
sys.stdout.write('%s: Ignored XML file ‘%s’; already has '
'a note\n' %
(latest_tag, api_xml_basename))
continue
notes = _get_contents_of_file(args, latest_tag, api_xml_file)
subprocess.check_output(_git_command(args, 'notes') +
['--ref',
'refs/%s/%s' %
(args.dbus_api_git_refs,
api_xml_basename),
'add', '-C', notes, latest_tag])
sys.stdout.write('%s: Added note ‘%s’ for XML file ‘%s’\n' %
(latest_tag, notes, api_xml_basename))
except subprocess.CalledProcessError:
sys.stderr.write('error: Failed to store notes for API file '
'‘%s’ and git tag ‘%s’.\n' %
(api_xml_file, latest_tag))
return 1
# Push to the remote
try:
_push_notes(args)
except subprocess.CalledProcessError:
sys.stderr.write('error: Failed to push notes to remote ‘%s’.\n' %
args.git_remote_origin)
return 1
return 0
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
def command_check(args):
"""
Check for API differences between two tags.
If old_ref is not specified, it defaults to the latest signed tag. If
new_ref is not specified, it defaults to the git work tree.
"""
if args.old_ref != '' and not _is_release(args, args.old_ref):
sys.stderr.write('error: Invalid --old-ref ‘%s’\n' % args.old_ref)
return 1
if args.new_ref != '' and not _is_release(args, args.new_ref):
sys.stderr.write('error: Invalid --new-ref ‘%s’\n' % args.new_ref)
return 1
try:
_fetch_notes(args)
except subprocess.CalledProcessError:
# Continue anyway
sys.stderr.write('error: Failed to fetch latest refs.\n')
old_ref = args.old_ref
new_ref = args.new_ref
if old_ref == '':
# Get the latest git tag
try:
old_ref = _get_latest_release(args)
except subprocess.CalledProcessError:
sys.stderr.write('error: Failed to find latest git tag.\n')
return 1
try:
refs = subprocess.check_output(_git_command(args, 'for-each-ref') +
['--format=%(refname)',
'refs/%s' % args.dbus_api_git_refs])
refs = refs.strip().decode('utf-8').split('\n')
except subprocess.CalledProcessError:
sys.stderr.write('error: Failed to get ref list.\n')
return 1
retval = 0
for note_ref in refs:
api_xml_basename = os.path.basename(note_ref)
if args.silent:
sys.stdout.write(' DIFF %s\n' % api_xml_basename)
else:
sys.stdout.write('Comparing %s\n' % api_xml_basename)
with named_pipe() as old_pipe_path, named_pipe() as new_pipe_path:
old_notes_filename = old_pipe_path
if new_ref == '':
new_notes_filename = \
_get_notes_filename_for_head(args, api_xml_basename)
else:
new_notes_filename = new_pipe_path
diff_command = ['dbus-interface-diff',
'--warnings', args.warnings,
'--file-display-name', api_xml_basename]
if args.fatal_warnings:
diff_command += ['--fatal-warnings']
diff_command += [old_notes_filename, new_notes_filename]
old_notes_command = _git_command(args, 'notes') + [
'--ref',
'refs/%s/%s' % (args.dbus_api_git_refs, api_xml_basename),
'show', old_ref,
]
new_notes_command = _git_command(args, 'notes') + [
'--ref',
'refs/%s/%s' % (args.dbus_api_git_refs, api_xml_basename),
'show', new_ref,
]
diff_proc = subprocess.Popen(diff_command)
with open(old_pipe_path, 'wb') as old_pipe, \
open(os.devnull, 'w') as dev_null:
old_notes_proc = subprocess.Popen(old_notes_command,
stdout=old_pipe,
stderr=dev_null)
if new_ref == '':
new_notes_proc = None
# Debug output. Roughly equivalent to `set -v`.
if not args.silent:
ls_files_command = _git_command(args, 'ls-files') + [
'--full-name',
'*/%s' % api_xml_basename,
]
if args.git_work_tree != '':
git_work_tree = args.git_work_tree + '/'
else:
git_work_tree = ''
sys.stdout.write('%s \\\n'
' <(%s) \\\n'
' %s`%s`\n' %
(_format_command(diff_command[:-2]),
_format_command(old_notes_command),
git_work_tree,
_format_command(ls_files_command)))
else:
with open(new_pipe_path, 'wb') as new_pipe:
new_notes_proc = subprocess.Popen(new_notes_command,
stdout=new_pipe,
stderr=dev_null)
# Debug output. Roughly equivalent to `set -v`.
if not args.silent:
sys.stdout.write('%s \\\n'
' <(%s) \\\n'
' <(%s)\n' %
(_format_command(diff_command[:-2]),
_format_command(old_notes_command),
_format_command(new_notes_command)))
old_notes_proc.communicate()
old_notes_proc.wait()
if new_notes_proc is not None:
new_notes_proc.wait()
diff_proc.wait()
# Output the status from the first failure
if retval == 0 and diff_proc.returncode != 0:
retval = diff_proc.returncode
return retval
def command_install(args):
"""Set up the API signature database for all existing signed tags."""
try:
tag_list = subprocess.check_output(_git_command(args, 'tag'))
tag_list = tag_list.strip().decode('utf-8').split('\n')
except subprocess.CalledProcessError:
sys.stderr.write('error: Failed to get tag list.\n')
return 1
for tag in tag_list:
outputted = False
for api_xml_file in args.dbus_api_xml_files:
api_xml_basename = os.path.basename(api_xml_file)
try:
notes = _get_contents_of_file(args, tag, api_xml_file)
except subprocess.CalledProcessError:
# Ignore it.
notes = ''
if notes == '':
continue
try:
_set_notes_for_ref(args, tag, api_xml_basename, notes)
sys.stdout.write('%s: Added note ‘%s’ for XML file ‘%s’\n' %
(tag, notes, api_xml_basename))
outputted = True
except subprocess.CalledProcessError:
# Ignore it
continue
if not outputted:
sys.stdout.write('%s: Nothing to do\n' % tag)
# Push the new refs
try:
_push_notes(args)
except subprocess.CalledProcessError:
sys.stderr.write('error: Failed to push notes to remote ‘%s’.\n' %
args.git_remote_origin)
return 1
return 0
def main():
"""Main helper implementation."""
# Parse command line arguments.
parser = argparse.ArgumentParser(
description='Comparing D-Bus interface definitions')
# Common arguments
parser.add_argument('--silent', action='store_const', const=True,
default=False,
help='Silence all non-error output')
# pylint: disable=bad-continuation
parser.add_argument('--git', type=str, default='git', metavar='COMMAND',
help='Path to the git command, including extra '
'arguments')
parser.add_argument('--git-dir', type=str, default='', metavar='PATH',
help='Path to the git directory in the project '
'checkout')
parser.add_argument('--git-work-tree', type=str, default='',
metavar='PATH',
help='Path to the git work tree for the project')
parser.add_argument('--git-remote', dest='git_remote_origin', type=str,
default='origin', metavar='REMOTE',
help='git remote to push notes to')
# pylint: disable=bad-continuation
parser.add_argument('--git-refs', dest='dbus_api_git_refs', type=str,
default='notes/dbus/api', metavar='REF-PATH',
help='Path beneath refs/ where the git notes will be'
' stored containing the API signatures database')
parser.add_argument('--no-push', action='store_const', const=True,
default=False,
help='Disable automatic pushing the API signature '
'database to a remote repository')
subparsers = parser.add_subparsers()
# dist command
parser_dist = subparsers.add_parser('dist')
parser_dist.add_argument('dbus_api_xml_files', metavar='API-FILE',
type=str, nargs='+',
help='D-Bus API XML file to check')
parser_dist.add_argument('--ignore-existing', action='store_const',
const=True, default=False,
help='Ignore existing API signatures rather than '
'erroring')
parser_dist.set_defaults(func=command_dist)
# check command
parser_check = subparsers.add_parser('check')
parser_check.add_argument('--diff-warnings', dest='warnings', type=str,
default='all',
help='Comma-separated list of warnings to '
'enable when running dbus-interface-diff')
parser_check.add_argument('--fatal-warnings', action='store_const',
const=True, default=False,
help='Treat all warnings as fatal')
# pylint: disable=bad-continuation
parser_check.add_argument('old_ref', metavar='OLD-REF',
type=str, nargs='?', default='',
help='Old ref to compare; or empty for the '
'latest signed tag')
parser_check.add_argument('new_ref', metavar='NEW-REF',
type=str, nargs='?', default='',
help='New ref to compare; or empty for HEAD')
parser_check.set_defaults(func=command_check)
# install command
parser_install = subparsers.add_parser('install')
parser_install.add_argument('dbus_api_xml_files', metavar='API-FILE',
type=str, nargs='+',
help='D-Bus API XML file to install')
parser_install.set_defaults(func=command_install)
args = parser.parse_args()
# Bail early if the .git directory does not exist, since that's our data
# store.
git_dir = args.git_dir
if git_dir == '':
git_dir = os.path.join(args.git_work_tree, '.git')
if not os.path.isdir(git_dir):
sys.stderr.write('error: Could not find git directory ‘%s’. '
'Skipping.\n' % git_dir)
return 0
return args.func(args)
if __name__ == '__main__':
main()
dbus-deviation-0.6.0/dbusdeviation/__init__.py 0000664 0001750 0001750 00000000000 13026466535 022201 0 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/dbusdeviation/tests/ 0000775 0001750 0001750 00000000000 13035747236 021244 5 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/dbusdeviation/tests/test_interfacecomparator.py 0000775 0001750 0001750 00000062010 13026466535 026707 0 ustar philip philip 0000000 0000000 #!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright © 2015 Collabora Ltd.
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
Unit tests for dbus-interface-diff
"""
# pylint: disable=missing-docstring
from dbusapi.interfaceparser import InterfaceParser
from dbusdeviation.interfacecomparator import InterfaceComparator
import os
import tempfile
import unittest
def _create_temp_xml_file(xml):
"""Create a temporary XML file with the given contents."""
tmp_fd, tmp_name = tempfile.mkstemp(suffix='.xml', text=True)
xml_fd = os.fdopen(tmp_fd, 'wt')
xml_fd.write(xml)
xml_fd.close()
return tmp_name
# pylint: disable=too-many-public-methods
class TestComparatorErrors(unittest.TestCase):
"""Test log output from InterfaceComparator."""
def _test_comparator(self, old_xml, new_xml):
"""Build an InterfaceComparator for the two parsed XML snippets."""
old_tmpfile = _create_temp_xml_file(old_xml)
new_tmpfile = _create_temp_xml_file(new_xml)
old_parser = InterfaceParser(old_tmpfile)
new_parser = InterfaceParser(new_tmpfile)
old_interfaces = old_parser.parse()
new_interfaces = new_parser.parse()
os.unlink(new_tmpfile)
os.unlink(old_tmpfile)
self.assertEqual(old_parser.get_output(), [])
self.assertEqual(new_parser.get_output(), [])
self.assertNotEqual(old_interfaces, None)
self.assertNotEqual(new_interfaces, None)
return InterfaceComparator(old_interfaces, new_interfaces)
# pylint: disable=invalid-name
def assertOutput(self, old_xml, new_xml, output): # noqa
comparator = self._test_comparator(old_xml, new_xml)
self.assertEqual(comparator.compare(), output)
self.assertEqual(comparator.get_output(), output)
def test_interface_removed(self):
self.assertOutput(
" ",
" ", [
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'interface-removed',
'Interface ‘I.A’ has been removed.'),
])
def test_interface_added(self):
self.assertOutput(
" ",
" ", [
(None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE,
'interface-added',
'Interface ‘I.A’ has been added.'),
])
def test_interface_deprecated(self):
self.assertOutput(
" ",
""
" "
" ", [
(None, InterfaceComparator.OUTPUT_INFO,
'deprecated',
'Node ‘I.A’ has been deprecated.'),
])
def test_interface_deprecated_explicit(self):
self.assertOutput(
""
" "
" ",
""
" "
" ", [
(None, InterfaceComparator.OUTPUT_INFO,
'deprecated',
'Node ‘I.A’ has been deprecated.'),
])
def test_interface_undeprecated(self):
self.assertOutput(
""
" "
" ",
" ", [
(None, InterfaceComparator.OUTPUT_INFO,
'undeprecated',
'Node ‘I.A’ has been un-deprecated.'),
])
def test_interface_undeprecated_explicit(self):
self.assertOutput(
""
" "
" ",
""
" "
" ", [
(None, InterfaceComparator.OUTPUT_INFO,
'undeprecated',
'Node ‘I.A’ has been un-deprecated.'),
])
def test_interface_name_changed(self):
self.assertOutput(
" ",
" ", [
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'interface-removed',
'Interface ‘I.A’ has been removed.'),
(None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE,
'interface-added',
'Interface ‘I.A2’ has been added.')
])
def test_method_removed(self):
self.assertOutput(
" "
" ",
" ", [
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'method-removed',
'Method ‘I.A.M’ has been removed.'),
])
def test_method_added(self):
self.assertOutput(
" ",
" "
" ",
[
(None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE,
'method-added',
'Method ‘I.A.M’ has been added.'),
])
def test_method_name_changed(self):
self.assertOutput(
" "
" ",
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'method-removed',
'Method ‘I.A.M’ has been removed.'),
(None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE,
'method-added',
'Method ‘I.A.M2’ has been added.'),
])
def test_method_arg_removed(self):
self.assertOutput(
""
" "
" ",
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-removed',
'Argument 0 of method ‘I.A.M’ has been removed.'),
])
def test_method_arg_added(self):
self.assertOutput(
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-added',
'Argument 0 of method ‘I.A.M’ has been added.'),
])
def test_method_arg_name_changed(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_INFO,
'argument-name-changed',
'Argument 0 (‘A’) of method ‘I.A.M’ has changed name '
'from ‘A’ to ‘Z’.'),
])
def test_method_arg_type_changed(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-type-changed',
'Argument 0 of method ‘I.A.M’ has changed type from ‘s’ to '
'‘b’.'),
])
def test_method_arg_direction_changed(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-direction-changed-in-out',
'Argument 0 of method ‘I.A.M’ has changed direction '
'from ‘in’ to ‘out’.'),
])
def test_method_c_symbol_changed(self):
self.assertOutput(
""
" "
" ",
""
" "
" ", [
(None, InterfaceComparator.OUTPUT_INFO,
'c-symbol-changed',
'Node ‘I.A.M’ has changed its C symbol from ‘S1’ to ‘S2’.'),
])
def test_method_no_reply_changed_to_false(self):
self.assertOutput(
""
" "
" ",
""
" "
" ", [
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'reply-added',
'Node ‘I.A.M’ has been marked as returning a reply.'),
])
def test_method_no_reply_changed_to_true(self):
self.assertOutput(
" "
" ",
""
" "
" ", [
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'reply-removed',
'Node ‘I.A.M’ has been marked as not returning a reply.'),
])
def test_method_no_reply_changed_to_true_explicit(self):
self.assertOutput(
""
" "
" ",
""
" "
" ", [
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'reply-removed',
'Node ‘I.A.M’ has been marked as not returning a reply.'),
])
def test_property_removed(self):
self.assertOutput(
""
" "
" ",
" ", [
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'property-removed',
'Property ‘I.A.P’ has been removed.'),
])
def test_property_added(self):
self.assertOutput(
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE,
'property-added',
'Property ‘I.A.P’ has been added.'),
])
def test_property_name_changed(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'property-removed',
'Property ‘I.A.P’ has been removed.'),
(None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE,
'property-added',
'Property ‘I.A.P2’ has been added.'),
])
def test_property_type_changed(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'property-type-changed',
'Property ‘I.A.P’ has changed type from ‘b’ to ‘s’.'),
])
def test_property_access_changed_r_to_rw(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE,
'property-access-changed-read-readwrite',
'Property ‘I.A.P’ has changed access from ‘read’ to '
'‘readwrite’, becoming less restrictive.'),
])
def test_property_access_changed_w_to_rw(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE,
'property-access-changed-write-readwrite',
'Property ‘I.A.P’ has changed access from ‘write’ to '
'‘readwrite’, becoming less restrictive.'),
])
def test_property_access_changed_r_to_w(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'property-access-changed-read-write',
'Property ‘I.A.P’ has changed access '
'from ‘read’ to ‘write’.'),
])
def test_property_access_changed_rw_to_r(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'property-access-changed-readwrite-read',
'Property ‘I.A.P’ has changed access from ‘readwrite’ to '
'‘read’.'),
])
def test_property_access_changed_rw_to_w(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'property-access-changed-readwrite-write',
'Property ‘I.A.P’ has changed access from ‘readwrite’ to '
'‘write’.'),
])
def test_property_emits_changed_signal_changed(self):
# All the classes of error we expect.
error1 = (
InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'Node ‘I.A.P’ started emitting '
'org.freedesktop.DBus.Properties.PropertiesChanged.'
)
error2a = (
InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'Node ‘I.A.P’ stopped emitting its new value in '
'org.freedesktop.DBus.Properties.PropertiesChanged.'
)
error2b = (
InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'Node ‘I.A.P’ started emitting its new value in '
'org.freedesktop.DBus.Properties.PropertiesChanged.'
)
error3 = (
InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE,
'Node ‘I.A.P’ stopped emitting '
'org.freedesktop.DBus.Properties.PropertiesChanged.'
)
error4a = (
InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'Node ‘I.A.P’ stopped being a constant.'
)
error4b = (
InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE,
'Node ‘I.A.P’ became a constant.'
)
# 2D matrix of test vectors. Each row gives the old annotation value;
# each column the new annotation value. Rows and columns are labelled
# by the corresponding index in labels. e.g. We expect error2a when
# changing annotation value from 'true' to 'invalidates'.
labels = ['true', 'invalidates', 'const', 'false']
vectors = [
[None, error2a, error3, error3],
[error2b, None, error3, error3],
[error1, error1, None, error4a],
[error1, error1, error4b, None],
]
for i in range(0, len(vectors)):
for j in range(0, len(vectors[0])):
vector = vectors[i][j]
if vector is not None:
vector = (
None,
vector[0],
'ecs-changed-%s-%s' % (labels[i], labels[j]),
vector[1],
)
expected_errors = [vector] if vector is not None else []
self.assertOutput(
""
""
" "
" "
" " % labels[i],
""
""
" "
" "
" " % labels[j],
expected_errors)
def test_signal_removed(self):
self.assertOutput(
" "
" ",
" ", [
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'signal-removed',
'Signal ‘I.A.S’ has been removed.'),
])
def test_signal_added(self):
self.assertOutput(
" ",
" "
" ",
[
(None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE,
'signal-added',
'Signal ‘I.A.S’ has been added.'),
])
def test_signal_name_changed(self):
self.assertOutput(
" "
" ",
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'signal-removed',
'Signal ‘I.A.S’ has been removed.'),
(None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE,
'signal-added',
'Signal ‘I.A.S2’ has been added.'),
])
def test_signal_arg_removed(self):
self.assertOutput(
""
" "
" ",
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-removed',
'Argument 0 of signal ‘I.A.S’ has been removed.'),
])
def test_signal_arg_added(self):
self.assertOutput(
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-added',
'Argument 0 of signal ‘I.A.S’ has been added.'),
])
def test_signal_arg_name_changed(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_INFO,
'argument-name-changed',
'Argument 0 (‘A’) of signal ‘I.A.S’ has changed name '
'from ‘A’ to ‘Z’.'),
])
def test_signal_arg_type_changed(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-type-changed',
'Argument 0 of signal ‘I.A.S’ has changed type from ‘s’ to '
'‘b’.'),
])
def test_signal_arg_direction_changed(self):
self.assertOutput(
""
" "
" ",
""
" "
" ",
[
(None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE,
'argument-direction-changed-in-out',
'Argument 0 of signal ‘I.A.S’ has changed direction '
'from ‘in’ to ‘out’.'),
])
if __name__ == '__main__':
# Run test suite
unittest.main()
dbus-deviation-0.6.0/dbusdeviation/tests/__init__.py 0000664 0001750 0001750 00000000000 13026466535 023343 0 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/COPYING 0000664 0001750 0001750 00000063472 13026466535 016311 0 ustar philip philip 0000000 0000000 GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
Copyright (C)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
dbus-deviation-0.6.0/AUTHORS 0000664 0001750 0001750 00000000062 13026466535 016310 0 ustar philip philip 0000000 0000000 Philip Withnall
dbus-deviation-0.6.0/README 0000664 0001750 0001750 00000007210 13026466535 016122 0 ustar philip philip 0000000 0000000 dbus-deviation
==============
dbus-deviation is a project for parsing D-Bus introspection XML and processing
it in various ways. Its main tool is dbus-interface-diff, which calculates the
difference between two D-Bus APIs for the purpose of checking for API breaks.
This functionality is also available as a Python module, dbusdeviation.
A second Python module, dbusapi, is provided for parsing D-Bus introspection
XML to produce an AST representing a D-Bus interface.
dbus-deviation’s API is currently unstable and is likely to change wildly.
Using dbus-deviation
====================
dbus-deviation can be used as a utility program or as a Python library.
The utility programs:
• dbus-interface-diff:
Check for differences between two versions of the same D-Bus API and
print details of each difference. It can check for problems with forwards
and backwards compatibility, as well as general informational
differences.
Example:
dbus-interface-diff \
com.example.Interface1.xml \ # old version of the interface
com.example.Interface2.xml # new version of the interface
• dbus-interface-vcs-helper:
This is a helper program designed to be used by dbus-deviation.mk.
As a library, the core object is an InterfaceParser, allowing a D-Bus API to
be parsed and represented as an AST. See the API documentation for more
explanation and examples.
dbus-deviation.mk
-----------------
This is a Makefile snippet which should be copied into your project, added to
git, and the following two lines included in your top-level Makefile.am:
dbus_api_xml_files = list of D-Bus interface XML files
-include $(top_srcdir)/dbus-deviation.mk
Do not add it to EXTRA_DIST. It is designed to work from git checkouts only.
Then run:
make dbus-deviation-mk-install
to set up the API signature database. This assumes that your project defines
D-Bus interfaces in XML files, and does not generate them at runtime.
Finally, copy pre-push.hook to .git/hooks/pre-push and ensure it’s executable.
This script will automatically update the API signature database when a new
release tag is pushed to the git remote.
There is currently no streamlined support for projects which generate D-Bus
interfaces at runtime.
dbus-deviation.mk defines the following rules:
• dist-dbus-api-compatibility (a dependency of dist-hook)
• check-dbus-api-compatibility (a dependency of check-local)
• dbus-deviation-mk-install (never triggered automatically)
Dependencies
============
• argparse
• lxml
Development
===========
For fun, dbus-deviation uses the following services to do continuous
integration and gather build statistics:
• https://travis-ci.org/pwithnall/dbus-deviation
• https://landscape.io/github/pwithnall/dbus-deviation
• https://coveralls.io/r/pwithnall/dbus-deviation
• https://codecov.io/github/pwithnall/dbus-deviation
Licensing
=========
dbus-deviation is licensed under the LGPL version 2.1 (or, at your option, any
later version). See COPYING for more details.
dbus-deviation versions 0.4.0 and earlier were licensed under the choice of the
Academic Free License version 2.1, or the GNU General Public License version 2
(or, at your option, any later version). This is the same license as D-Bus
itself. Version 0.5.0 was relicensed to LGPLv2.1+ as it’s a more standard license
with less ambiguity about its implications.
Bugs
====
Bug reports and patches should be sent via GitHub or Gitlab:
https://github.com/pwithnall/dbus-deviation
https://gitlab.com/dbus-deviation/dbus-deviation
Contact
=======
Philip Withnall
http://people.collabora.com/~pwith/dbus-deviation/
dbus-deviation-0.6.0/NEWS 0000664 0001750 0001750 00000003272 13035745151 015737 0 ustar philip philip 0000000 0000000 Overview of changes from dbus-deviation 0.5.0 to dbus-deviation 0.6.0
=====================================================================
Major changes:
• Split out the logging API to allow for reuse
• Expose source line numbers in dbusapi.ast
• Add a D-Bus type parser, dbusapi.typeparser (thanks to Kaloyan Tenchov)
• Drop Python 3.3 support as Sphinx no longer supports it
• Add a D-Bus type formatter, dbusapi.typeformatter
Overview of changes from dbus-deviation 0.4.0 to dbus-deviation 0.5.0
=====================================================================
Major changes:
• Add support for elements (Kaloyan Tenchov)
• Add object path, interface name and callable name validation
(Kaloyan Tenchov)
• Relicense to LGPLv2.1+
Overview of changes from dbus-deviation 0.3.0 to dbus-deviation 0.4.0
=====================================================================
Major changes:
• Improve test coverage
• Port to lxml instead of xml.etree
• Support parsing comments and documentation nodes
Overview of changes from dbus-deviation 0.2.0 to dbus-deviation 0.3.0
=====================================================================
Major changes:
• Improve utility arguments and variables in dbus-deviation.mk and vcs-helper
Overview of changes from dbus-deviation 0.1.0 to dbus-deviation 0.2.0
=====================================================================
Major changes:
• Improve error reporting
• Improve test coverage
• Add Makefile snippet and helper program (vcs-helper) for use in other
projects
Initial release of dbus-deviation 0.1.0
=======================================
Major changes:
• Initial version of the project
dbus-deviation-0.6.0/.landscape.yaml 0000664 0001750 0001750 00000000274 13026466535 020141 0 ustar philip philip 0000000 0000000 doc-warnings: true
test-warnings: false
strictness: veryhigh
pep8:
full: true
pep257:
disable:
- D211
- D400
ignore-paths:
- docs/conf.py
- setup.py
- version.py
dbus-deviation-0.6.0/setup.py 0000775 0001750 0001750 00000006563 13026500444 016755 0 ustar philip philip 0000000 0000000 #!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright © 2015 Collabora Ltd.
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see .
"""
Parse D-Bus introspection XML and process it in various ways
"""
from setuptools import setup, find_packages
import os
import version # https://gist.github.com/pwithnall/7bc5f320b3bdf418265a
project_name = 'dbus-deviation'
__version__ = version.get_version()
project_author = 'Philip Withnall'
README = open('README').read()
NEWS = open('NEWS').read()
# From http://stackoverflow.com/a/17004263/2931197
def discover_and_run_tests():
import os
import sys
import unittest
# get setup.py directory
setup_file = sys.modules['__main__'].__file__
setup_dir = os.path.abspath(os.path.dirname(setup_file))
# use the default shared TestLoader instance
test_loader = unittest.defaultTestLoader
# use the basic test runner that outputs to sys.stderr
test_runner = unittest.TextTestRunner()
# automatically discover all tests
# NOTE: only works for python 2.7 and later
test_suite = test_loader.discover(setup_dir)
# run the test suite
test_runner.run(test_suite)
try:
from setuptools.command.test import test
class DiscoverTest(test):
def finalize_options(self):
test.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
discover_and_run_tests()
except ImportError:
from distutils.core import Command
class DiscoverTest(Command):
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
discover_and_run_tests()
setup(
name=project_name,
version=__version__,
packages=find_packages(exclude=['*.tests']),
include_package_data=True,
exclude_package_data={'': ['.gitignore']},
zip_safe=True,
setup_requires=[
'setuptools_git >= 0.3',
'setuptools_pep8',
'sphinx',
],
install_requires=['lxml'],
tests_require=[],
entry_points={
'console_scripts': [
'dbus-interface-diff = dbusdeviation.utilities.diff:main',
'dbus-interface-vcs-helper = '
'dbusdeviation.utilities.vcs_helper:main',
],
},
author=project_author,
author_email='philip.withnall@collabora.co.uk',
description=__doc__,
long_description=README + '\n\n' + NEWS,
license='LGPLv2.1+',
url='http://people.collabora.com/~pwith/dbus-deviation/',
cmdclass={'test': DiscoverTest},
command_options={
'build_sphinx': {
'project': ('setup.py', project_name),
'version': ('setup.py', __version__),
'release': ('setup.py', __version__),
},
},
)
dbus-deviation-0.6.0/.travis.yml 0000664 0001750 0001750 00000001452 13035123314 017336 0 ustar philip philip 0000000 0000000 language: python
python:
- "2.7"
- "3.4"
- "3.5"
# Disabled due to lxml 3.5.0 not working with PyPy:
# See: https://bitbucket.org/pypy/compatibility/wiki/lxml
# - "pypy"
# Disabled due to bugs in the logilab package on the Python 3.2 build slave.
# See: https://travis-ci.org/pwithnall/dbus-deviation/jobs/60500460
# - "3.2"
# command to install dependencies
install:
- pip install pylint coverage coveralls codecov .
# command to run tests
script:
- coverage run --source dbusapi,dbusdeviation --omit 'dbusdeviation/utilities/*' setup.py test
- coverage report --fail-under=70
- python setup.py check
- python setup.py pep8
- pylint --errors-only dbusapi
- pylint --errors-only dbusdeviation
# Submit coverage data to coveralls.io and codecov.io
after_success:
- coveralls
- codecov
dbus-deviation-0.6.0/.be/ 0000775 0001750 0001750 00000000000 13035747236 015706 5 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/.be/version 0000664 0001750 0001750 00000000037 13026466535 017316 0 ustar philip philip 0000000 0000000 Bugs Everywhere Directory v1.5
dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/ 0000775 0001750 0001750 00000000000 13035747236 023146 5 ustar philip philip 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ 0000775 0001750 0001750 00000000000 13035747236 024106 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000151 00000000000 011212 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714c0000775 0001750 0001750 00000000000 13035747236 030340 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011214 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/comments/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714c0000775 0001750 0001750 00000000000 13035747236 030340 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000227 00000000000 011216 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/comments/2cedc3e0-2161-4efa-8440-0c7a79eafe41/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714c0000775 0001750 0001750 00000000000 13035747236 030340 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000233 00000000000 011213 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/comments/2cedc3e0-2161-4efa-8440-0c7a79eafe41/body dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714c0000664 0001750 0001750 00000001045 13026466535 030342 0 ustar philip philip 0000000 0000000 Support projects which generate their D-Bus introspection data at runtime, and define their D-Bus APIs using C structs, rather than static XML files.
This requires building and running the project, then calling the D-Bus Introspect() method on it to get the API signature to put in the database.;
Build a tool which stores given generated files, then lists the remaining tags which don't have API signatures stored for them; doing something stateful like `git rebase -i` would be too complex to integrate with a build process for a first attempt.
././@LongLink 0000000 0000000 0000000 00000000235 00000000000 011215 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/comments/2cedc3e0-2161-4efa-8440-0c7a79eafe41/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714c0000664 0001750 0001750 00000000261 13026466535 030341 0 ustar philip philip 0000000 0000000 {
"Author": "Philip Withnall ",
"Content-type": "text/plain",
"Date": "Thu, 14 May 2015 16:37:50 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011220 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714c0000664 0001750 0001750 00000000527 13026466535 030346 0 ustar philip philip 0000000 0000000 {
"creator": "Philip Withnall ",
"reporter": "Philip Withnall ",
"severity": "minor",
"status": "open",
"summary": "Add a helper for dynamically generated APIs",
"time": "Thu, 14 May 2015 16:36:29 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000151 00000000000 011212 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda0000775 0001750 0001750 00000000000 13035747236 030512 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011214 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/comments/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda0000775 0001750 0001750 00000000000 13035747236 030512 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000227 00000000000 011216 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/comments/44b1317e-bff8-477e-9d13-50071beb825b/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda0000775 0001750 0001750 00000000000 13035747236 030512 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000233 00000000000 011213 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/comments/44b1317e-bff8-477e-9d13-50071beb825b/body dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda0000664 0001750 0001750 00000000273 13026466535 030516 0 ustar philip philip 0000000 0000000 Improve the output formatting so it’s nicer to read. Give each log message an ID which can be used to refer to documentation, and which can be used to individually disable the message.
././@LongLink 0000000 0000000 0000000 00000000235 00000000000 011215 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/comments/44b1317e-bff8-477e-9d13-50071beb825b/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda0000664 0001750 0001750 00000000261 13026466535 030513 0 ustar philip philip 0000000 0000000 {
"Author": "Philip Withnall ",
"Content-type": "text/plain",
"Date": "Wed, 29 Apr 2015 14:29:39 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011220 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda0000664 0001750 0001750 00000000506 13026466535 030515 0 ustar philip philip 0000000 0000000 {
"creator": "Philip Withnall ",
"reporter": "Philip Withnall ",
"severity": "minor",
"status": "fixed",
"summary": "Improve output formatting",
"time": "Wed, 29 Apr 2015 14:28:54 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000151 00000000000 011212 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6b0000775 0001750 0001750 00000000000 13035747236 030342 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011214 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/comments/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6b0000775 0001750 0001750 00000000000 13035747236 030342 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000227 00000000000 011216 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/comments/11c90632-88a0-4999-bfa4-19fd5857291e/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6b0000775 0001750 0001750 00000000000 13035747236 030342 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000233 00000000000 011213 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/comments/11c90632-88a0-4999-bfa4-19fd5857291e/body dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6b0000664 0001750 0001750 00000000526 13026466535 030347 0 ustar philip philip 0000000 0000000 Allow different D-Bus interfaces to have different stability policies, so that one interface could be stable while another is unstable, and both be checked by dbus-interface-diff.
In fact, it might make sense to add an attribute to the D-Bus introspection XML files which states the stability of each interface in a machine readable manner.
././@LongLink 0000000 0000000 0000000 00000000235 00000000000 011215 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/comments/11c90632-88a0-4999-bfa4-19fd5857291e/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6b0000664 0001750 0001750 00000000261 13026466535 030343 0 ustar philip philip 0000000 0000000 {
"Author": "Philip Withnall ",
"Content-type": "text/plain",
"Date": "Mon, 01 Jun 2015 16:29:58 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011220 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6b0000664 0001750 0001750 00000000537 13026466535 030351 0 ustar philip philip 0000000 0000000 {
"creator": "Philip Withnall ",
"reporter": "Philip Withnall ",
"severity": "minor",
"status": "open",
"summary": "Allow stability policy to differ between interfaces",
"time": "Mon, 01 Jun 2015 16:28:48 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000151 00000000000 011212 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000775 0001750 0001750 00000000000 13035747236 030320 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011214 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000775 0001750 0001750 00000000000 13035747236 030320 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000227 00000000000 011216 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/0b90dd22-dcf6-43e7-88a2-b72bdcef61c9/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000775 0001750 0001750 00000000000 13035747236 030320 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000233 00000000000 011213 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/0b90dd22-dcf6-43e7-88a2-b72bdcef61c9/body dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000664 0001750 0001750 00000000076 13026466535 030325 0 ustar philip philip 0000000 0000000 Fixed with revision 720a0d94c01626d6f086feb47793d863d8bb5573.
././@LongLink 0000000 0000000 0000000 00000000235 00000000000 011215 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/0b90dd22-dcf6-43e7-88a2-b72bdcef61c9/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000664 0001750 0001750 00000000261 13026466535 030321 0 ustar philip philip 0000000 0000000 {
"Author": "Philip Withnall ",
"Content-type": "text/plain",
"Date": "Thu, 07 May 2015 18:31:31 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000227 00000000000 011216 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/43ad8034-30c8-42f3-9c0d-61f13d9b8537/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000775 0001750 0001750 00000000000 13035747236 030320 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000233 00000000000 011213 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/43ad8034-30c8-42f3-9c0d-61f13d9b8537/body dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000664 0001750 0001750 00000000331 13026466535 030317 0 ustar philip philip 0000000 0000000 Add autotools macros to support the git integration workflow suggested here:
https://bugs.freedesktop.org/show_bug.cgi?id=89845#c2
Document the whole thing really well and port a few modules to it as an example.
././@LongLink 0000000 0000000 0000000 00000000235 00000000000 011215 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/43ad8034-30c8-42f3-9c0d-61f13d9b8537/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000664 0001750 0001750 00000000261 13026466535 030321 0 ustar philip philip 0000000 0000000 {
"Author": "Philip Withnall ",
"Content-type": "text/plain",
"Date": "Wed, 29 Apr 2015 13:46:43 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011220 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000664 0001750 0001750 00000000527 13026466535 030326 0 ustar philip philip 0000000 0000000 {
"creator": "Philip Withnall ",
"reporter": "Philip Withnall ",
"severity": "minor",
"status": "fixed",
"summary": "Add autotools integration for git workflow",
"time": "Wed, 29 Apr 2015 13:45:38 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000151 00000000000 011212 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1660000775 0001750 0001750 00000000000 13035747236 030445 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011214 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/comments/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1660000775 0001750 0001750 00000000000 13035747236 030445 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000227 00000000000 011216 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/comments/4581dbb2-105c-42f8-8858-7b8a4071a058/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1660000775 0001750 0001750 00000000000 13035747236 030445 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000233 00000000000 011213 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/comments/4581dbb2-105c-42f8-8858-7b8a4071a058/body dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1660000664 0001750 0001750 00000000325 13026466535 030447 0 ustar philip philip 0000000 0000000 Add a helper for use with `git bisect` to work out which commit broke D-Bus API compatibility. This is mostly useful for repositories which define their D-Bus APIs in code, rather than as introspection XML files.
././@LongLink 0000000 0000000 0000000 00000000235 00000000000 011215 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/comments/4581dbb2-105c-42f8-8858-7b8a4071a058/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1660000664 0001750 0001750 00000000261 13026466535 030446 0 ustar philip philip 0000000 0000000 {
"Author": "Philip Withnall ",
"Content-type": "text/plain",
"Date": "Fri, 29 Apr 2016 16:36:26 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011220 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1660000664 0001750 0001750 00000000502 13026466535 030444 0 ustar philip philip 0000000 0000000 {
"creator": "Philip Withnall ",
"reporter": "Philip Withnall ",
"severity": "minor",
"status": "open",
"summary": "Add git bisect support",
"time": "Fri, 29 Apr 2016 16:35:31 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000151 00000000000 011212 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000775 0001750 0001750 00000000000 13035747236 030732 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011214 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000775 0001750 0001750 00000000000 13035747236 030732 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000227 00000000000 011216 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/91b7a01a-4b27-4617-900f-fd1243852d04/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000775 0001750 0001750 00000000000 13035747236 030732 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000233 00000000000 011213 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/91b7a01a-4b27-4617-900f-fd1243852d04/body dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000664 0001750 0001750 00000000242 13026466535 030732 0 ustar philip philip 0000000 0000000 Add dbus-deviation to http://coveralls.io once it’s back online.
See also: https://github.com/audreyr/how-to/blob/master/python/use_coverage_with_unittest.rst
././@LongLink 0000000 0000000 0000000 00000000235 00000000000 011215 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/91b7a01a-4b27-4617-900f-fd1243852d04/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000664 0001750 0001750 00000000261 13026466535 030733 0 ustar philip philip 0000000 0000000 {
"Author": "Philip Withnall ",
"Content-type": "text/plain",
"Date": "Wed, 29 Apr 2015 13:52:42 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000227 00000000000 011216 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/1ae388e0-0d45-4faf-8744-471c338140c3/ dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000775 0001750 0001750 00000000000 13035747236 030732 5 ustar philip philip 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000233 00000000000 011213 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/1ae388e0-0d45-4faf-8744-471c338140c3/body dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000664 0001750 0001750 00000000074 13026466535 030735 0 ustar philip philip 0000000 0000000 Fixed with commit 423e1b1adac21d8a9d4d97fd15d6099654c259d0.
././@LongLink 0000000 0000000 0000000 00000000235 00000000000 011215 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/1ae388e0-0d45-4faf-8744-471c338140c3/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000664 0001750 0001750 00000000261 13026466535 030733 0 ustar philip philip 0000000 0000000 {
"Author": "Philip Withnall ",
"Content-type": "text/plain",
"Date": "Thu, 14 May 2015 16:36:03 +0000"
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011220 L ustar 0000000 0000000 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/values dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000664 0001750 0001750 00000000500 13026466535 030727 0 ustar philip philip 0000000 0000000 {
"creator": "Philip Withnall ",
"reporter": "Philip Withnall ",
"severity": "minor",
"status": "fixed",
"summary": "Add to coveralls.io",
"time": "Wed, 29 Apr 2015 13:52:18 +0000"
}
dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/settings 0000664 0001750 0001750 00000000003 13026466535 024722 0 ustar philip philip 0000000 0000000 {}
dbus-deviation-0.6.0/version.py 0000664 0001750 0001750 00000003727 13026466535 017312 0 ustar philip philip 0000000 0000000 # This program is placed into the public domain.
"""
Gets the current version number.
If in a git repository, it is the current git tag.
Otherwise it is the one contained in the PKG-INFO file.
To use this script, simply import it in your setup.py file
and use the results of get_version() as your package version:
from version import *
setup(
...
version=get_version(),
...
)
"""
__all__ = ('get_version')
from os.path import dirname, isdir, join
import os
import re
import subprocess
version_re = re.compile('^Version: (.+)$', re.M)
def get_version():
d = dirname(__file__)
if isdir(join(d, '.git')):
# Get the version using "git describe".
cmd = 'git describe --tags --match [0-9]*'.split()
try:
version = subprocess.check_output(cmd).decode().strip()
except subprocess.CalledProcessError:
print('Unable to get version number from git tags')
exit(1)
# PEP 386 compatibility
if '-' in version:
version = '.post'.join(version.split('-')[:2])
# Don't declare a version "dirty" merely because a time stamp has
# changed. If it is dirty, append a ".dev1" suffix to indicate a
# development revision after the release.
with open(os.devnull, 'w') as fd_devnull:
subprocess.call(['git', 'status'],
stdout=fd_devnull, stderr=fd_devnull)
cmd = 'git diff-index --name-only HEAD'.split()
try:
dirty = subprocess.check_output(cmd).decode().strip()
except subprocess.CalledProcessError:
print('Unable to get git index status')
exit(1)
if dirty != '':
version += '.dev1'
else:
# Extract the version from the PKG-INFO file.
with open(join(d, 'PKG-INFO')) as f:
version = version_re.search(f.read()).group(1)
return version
if __name__ == '__main__':
print(get_version())