##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test XML configuration (ZCML) machinery.
$Id: test_xmlconfig.py 37395 2005-07-23 23:24:37Z philikon $
"""
import unittest
import os
from zope.testing.doctestunit import DocTestSuite
from zope.configuration import xmlconfig, config
from zope.configuration.tests.samplepackage import foo
from pprint import PrettyPrinter, pprint
class FauxLocator(object):
def __init__(self, file, line, column):
self.file, self.line, self.column = file, line, column
def getSystemId(self):
return self.file
def getLineNumber(self):
return self.line
def getColumnNumber(self):
return self.column
class FauxContext(object):
def setInfo(self, info):
self.info = info
def getInfo(self):
return self.info
def begin(self, name, data, info):
self.begin_args = name, data
self.info = info
def end(self):
self.end_called = 1
def path(*p):
return os.path.join(os.path.dirname(__file__), *p)
def test_ConfigurationHandler_normal():
"""
>>> context = FauxContext()
>>> locator = FauxLocator('tests//sample.zcml', 1, 1)
>>> handler = xmlconfig.ConfigurationHandler(context)
>>> handler.setDocumentLocator(locator)
>>> handler.startElementNS((u"ns", u"foo"), u"foo",
... {(u"xxx", u"splat"): u"splatv",
... (None, u"a"): u"avalue",
... (None, u"b"): u"bvalue",
... })
>>> context.info
File "tests//sample.zcml", line 1.1
>>> from pprint import PrettyPrinter
>>> pprint=PrettyPrinter(width=50).pprint
>>> pprint(context.begin_args)
((u'ns', u'foo'),
{'a': u'avalue', 'b': u'bvalue'})
>>> getattr(context, "end_called", 0)
0
>>> locator.line, locator.column = 7, 16
>>> handler.endElementNS((u"ns", u"foo"), u"foo")
>>> context.info
File "tests//sample.zcml", line 1.1-7.16
>>> context.end_called
1
"""
def test_ConfigurationHandler_err_start():
"""
>>> class FauxContext(FauxContext):
... def begin(self, *args):
... raise AttributeError("xxx")
>>> context = FauxContext()
>>> locator = FauxLocator('tests//sample.zcml', 1, 1)
>>> handler = xmlconfig.ConfigurationHandler(context)
>>> handler.setDocumentLocator(locator)
>>> try:
... v = handler.startElementNS((u"ns", u"foo"), u"foo",
... {(u"xxx", u"splat"): u"splatv",
... (None, u"a"): u"avalue",
... (None, u"b"): u"bvalue",
... })
... except xmlconfig.ZopeXMLConfigurationError, v:
... pass
>>> print v
File "tests//sample.zcml", line 1.1
AttributeError: xxx
"""
def test_ConfigurationHandler_err_end():
"""
>>> class FauxContext(FauxContext):
... def end(self):
... raise AttributeError("xxx")
>>> context = FauxContext()
>>> locator = FauxLocator('tests//sample.zcml', 1, 1)
>>> handler = xmlconfig.ConfigurationHandler(context)
>>> handler.setDocumentLocator(locator)
>>> handler.startElementNS((u"ns", u"foo"), u"foo",
... {(u"xxx", u"splat"): u"splatv",
... (None, u"a"): u"avalue",
... (None, u"b"): u"bvalue",
... })
>>> locator.line, locator.column = 7, 16
>>> try:
... v = handler.endElementNS((u"ns", u"foo"), u"foo")
... except xmlconfig.ZopeXMLConfigurationError, v:
... pass
>>> print v
File "tests//sample.zcml", line 1.1-7.16
AttributeError: xxx
"""
def clean_info_path(s):
part1 = s[:6]
part2 = s[6:s.find('"', 6)]
part2 = part2[part2.find("tests"):]
part2 = part2.replace(os.sep, '/')
part3 = s[s.find('"', 6):].rstrip()
return part1+part2+part3
def clean_path(s):
s = s[s.find("tests"):]
s = s.replace(os.sep, '/')
return s
def test_processxmlfile():
"""
>>> file = open(path("samplepackage", "configure.zcml"))
>>> context = config.ConfigurationMachine()
>>> xmlconfig.registerCommonDirectives(context)
>>> xmlconfig.processxmlfile(file, context)
>>> foo.data
[]
>>> context.execute_actions()
>>> data = foo.data.pop()
>>> data.args
(('x', 'blah'), ('y', 0))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/configure.zcml", line 12.2-12.29
>>> print clean_info_path(str(data.info))
File "tests/samplepackage/configure.zcml", line 12.2-12.29
>>> data.package
>>> data.basepath
"""
def test_file():
"""
>>> file_name = path("samplepackage", "configure.zcml")
>>> context = xmlconfig.file(file_name)
>>> data = foo.data.pop()
>>> data.args
(('x', 'blah'), ('y', 0))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/configure.zcml", line 12.2-12.29
>>> print clean_info_path(str(data.info))
File "tests/samplepackage/configure.zcml", line 12.2-12.29
>>> data.package
>>> print clean_path(data.basepath)
tests/samplepackage
"""
def test_include_by_package():
"""
>>> context = config.ConfigurationMachine()
>>> xmlconfig.registerCommonDirectives(context)
>>> import zope.configuration.tests.samplepackage as package
>>> xmlconfig.include(context, 'configure.zcml', package)
>>> context.execute_actions()
>>> data = foo.data.pop()
>>> data.args
(('x', 'blah'), ('y', 0))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/configure.zcml", line 12.2-12.29
>>> print clean_info_path(str(data.info))
File "tests/samplepackage/configure.zcml", line 12.2-12.29
>>> data.package is package
1
>>> data.basepath[-13:]
'samplepackage'
>>> [clean_path(p) for p in data.includepath]
['tests/samplepackage/configure.zcml']
"""
# Not any more
## Including the same file more than once produces an error:
## >>> try:
## ... xmlconfig.include(context, 'configure.zcml', package)
## ... except xmlconfig.ConfigurationError, e:
## ... 'OK'
## ...
## 'OK'
def test_include_by_file():
"""
>>> context = config.ConfigurationMachine()
>>> xmlconfig.registerCommonDirectives(context)
>>> here = os.path.dirname(__file__)
>>> path = os.path.join(here, "samplepackage", "foo.zcml")
>>> xmlconfig.include(context, path)
>>> context.execute_actions()
>>> data = foo.data.pop()
>>> data.args
(('x', 'foo'), ('y', 2))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/foo.zcml.in", line 12.2-12.28
>>> print clean_info_path(str(data.info))
File "tests/samplepackage/foo.zcml.in", line 12.2-12.28
>>> data.package
>>> data.basepath[-13:]
'samplepackage'
>>> [clean_path(p) for p in data.includepath]
['tests/samplepackage/foo.zcml.in']
"""
def test_include_by_file_glob():
"""
>>> context = config.ConfigurationMachine()
>>> xmlconfig.registerCommonDirectives(context)
>>> here = os.path.dirname(__file__)
>>> path = os.path.join(here, "samplepackage/baz*.zcml")
>>> xmlconfig.include(context, files=path)
>>> context.execute_actions()
>>> data = foo.data.pop()
>>> data.args
(('x', 'foo'), ('y', 3))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/baz3.zcml", line 5.2-5.28
>>> print clean_info_path(str(data.info))
File "tests/samplepackage/baz3.zcml", line 5.2-5.28
>>> data.package
>>> data.basepath[-13:]
'samplepackage'
>>> [clean_path(p) for p in data.includepath]
['tests/samplepackage/baz3.zcml']
>>> data = foo.data.pop()
>>> data.args
(('x', 'foo'), ('y', 2))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/baz2.zcml", line 5.2-5.28
>>> print clean_info_path(str(data.info))
File "tests/samplepackage/baz2.zcml", line 5.2-5.28
>>> data.package
>>> data.basepath[-13:]
'samplepackage'
>>> [clean_path(p) for p in data.includepath]
['tests/samplepackage/baz2.zcml']
"""
def clean_actions(actions):
return [
{'discriminator': discriminator,
'info': clean_info_path(`info`),
'includepath': [clean_path(p) for p in includepath],
}
for (discriminator, callable, args, kw, includepath, info, order)
in [config.expand_action(*action) for action in actions]
]
def clean_text_w_paths(error):
r = []
for line in unicode(error).split("\n"):
line = line.rstrip()
if not line:
continue
l = line.find('File "')
if l >= 0:
line = line[:l] + clean_info_path(line[l:])
r.append(line)
return '\n'.join(r)
def test_includeOverrides():
"""
When we have conflicting directives, we can resolve them if one of
the conflicting directives was from a file that included all of
the others. The problem with this is that this requires that all
of the overriding directives be in one file, typically the
top-most including file. This isn't very convenient. Fortunately,
we can overcome this with the includeOverrides directive. Let's
look at an example to see how this works.
Look at the file bar.zcml. It includes bar1.zcml and bar2.zcml.
bar2.zcml includes configure.zcml and has a foo
directive. bar2.zcml includes bar21.zcml. bar2.zcml has a foo
directive that conflicts with one in bar1.zcml. bar2.zcml also
overrides a foo directive in bar21.zcml. bar21.zcml has a foo
directive that conflicts with one in in configure.zcml. Whew!
Let's see what happens when we try to process bar.zcml.
>>> context = config.ConfigurationMachine()
>>> xmlconfig.registerCommonDirectives(context)
>>> here = os.path.dirname(__file__)
>>> path = os.path.join(here, "samplepackage", "bar.zcml")
>>> xmlconfig.include(context, path)
So far so good, let's look at the configuration actions:
>>> pprint=PrettyPrinter(width=70).pprint
>>> pprint(clean_actions(context.actions))
[{'discriminator': (('x', 'blah'), ('y', 0)),
'includepath': ['tests/samplepackage/bar.zcml',
'tests/samplepackage/bar1.zcml',
'tests/samplepackage/configure.zcml'],
'info': 'File "tests/samplepackage/configure.zcml", line 12.2-12.29'},
{'discriminator': (('x', 'blah'), ('y', 1)),
'includepath': ['tests/samplepackage/bar.zcml',
'tests/samplepackage/bar1.zcml'],
'info': 'File "tests/samplepackage/bar1.zcml", line 5.2-5.24'},
{'discriminator': (('x', 'blah'), ('y', 0)),
'includepath': ['tests/samplepackage/bar.zcml',
'tests/samplepackage/bar2.zcml',
'tests/samplepackage/bar21.zcml'],
'info': 'File "tests/samplepackage/bar21.zcml", line 3.2-3.24'},
{'discriminator': (('x', 'blah'), ('y', 2)),
'includepath': ['tests/samplepackage/bar.zcml',
'tests/samplepackage/bar2.zcml',
'tests/samplepackage/bar21.zcml'],
'info': 'File "tests/samplepackage/bar21.zcml", line 4.2-4.24'},
{'discriminator': (('x', 'blah'), ('y', 2)),
'includepath': ['tests/samplepackage/bar.zcml',
'tests/samplepackage/bar2.zcml'],
'info': 'File "tests/samplepackage/bar2.zcml", line 5.2-5.24'},
{'discriminator': (('x', 'blah'), ('y', 1)),
'includepath': ['tests/samplepackage/bar.zcml',
'tests/samplepackage/bar2.zcml'],
'info': 'File "tests/samplepackage/bar2.zcml", line 6.2-6.24'}]
As you can see, there are a number of conflicts (actions with the same
discriminator). Some of these can be resolved, but many can't, as
we'll find if we try to execuse the actions:
>>> try:
... v = context.execute_actions()
... except config.ConfigurationConflictError, v:
... pass
>>> print clean_text_w_paths(str(v))
Conflicting configuration actions
For: (('x', 'blah'), ('y', 0))
File "tests/samplepackage/configure.zcml", line 12.2-12.29
File "tests/samplepackage/bar21.zcml", line 3.2-3.24
For: (('x', 'blah'), ('y', 1))
File "tests/samplepackage/bar1.zcml", line 5.2-5.24
File "tests/samplepackage/bar2.zcml", line 6.2-6.24
Note that the conflicts for (('x', 'blah'), ('y', 2)) aren't
included in the error because they could be resolved.
Let's try this again using includeOverrides. We'll include
baro.zcml which includes bar2.zcml as overrides.
>>> context = config.ConfigurationMachine()
>>> xmlconfig.registerCommonDirectives(context)
>>> path = os.path.join(here, "samplepackage", "baro.zcml")
>>> xmlconfig.include(context, path)
Now, if we look at the actions:
>>> pprint(clean_actions(context.actions))
[{'discriminator': (('x', 'blah'), ('y', 0)),
'includepath': ['tests/samplepackage/baro.zcml',
'tests/samplepackage/bar1.zcml',
'tests/samplepackage/configure.zcml'],
'info': 'File "tests/samplepackage/configure.zcml", line 12.2-12.29'},
{'discriminator': (('x', 'blah'), ('y', 1)),
'includepath': ['tests/samplepackage/baro.zcml',
'tests/samplepackage/bar1.zcml'],
'info': 'File "tests/samplepackage/bar1.zcml", line 5.2-5.24'},
{'discriminator': (('x', 'blah'), ('y', 0)),
'includepath': ['tests/samplepackage/baro.zcml'],
'info': 'File "tests/samplepackage/bar21.zcml", line 3.2-3.24'},
{'discriminator': (('x', 'blah'), ('y', 2)),
'includepath': ['tests/samplepackage/baro.zcml'],
'info': 'File "tests/samplepackage/bar2.zcml", line 5.2-5.24'},
{'discriminator': (('x', 'blah'), ('y', 1)),
'includepath': ['tests/samplepackage/baro.zcml'],
'info': 'File "tests/samplepackage/bar2.zcml", line 6.2-6.24'}]
We see that:
- The conflicting actions between bar2.zcml and bar21.zcml have
been resolved, and
- The remaining (after conflict resolution) actions from bar2.zcml
and bar21.zcml have the includepath that they would have if they
were defined in baro.zcml and this override the actions from
bar1.zcml and configure.zcml.
We can now execute the actions without problem, since the
remaining conflicts are resolvable:
>>> context.execute_actions()
We should now have three entries in foo.data:
>>> len(foo.data)
3
>>> data = foo.data.pop(0)
>>> data.args
(('x', 'blah'), ('y', 0))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/bar21.zcml", line 3.2-3.24
>>> data = foo.data.pop(0)
>>> data.args
(('x', 'blah'), ('y', 2))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/bar2.zcml", line 5.2-5.24
>>> data = foo.data.pop(0)
>>> data.args
(('x', 'blah'), ('y', 1))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/bar2.zcml", line 6.2-6.24
We expect the exact same results when using includeOverrides with
the ``files`` argument instead of the ``file`` argument. The
baro2.zcml file uses the former:
>>> context = config.ConfigurationMachine()
>>> xmlconfig.registerCommonDirectives(context)
>>> path = os.path.join(here, "samplepackage", "baro2.zcml")
>>> xmlconfig.include(context, path)
Actions look like above:
>>> pprint(clean_actions(context.actions))
[{'discriminator': (('x', 'blah'), ('y', 0)),
'includepath': ['tests/samplepackage/baro2.zcml',
'tests/samplepackage/bar1.zcml',
'tests/samplepackage/configure.zcml'],
'info': 'File "tests/samplepackage/configure.zcml", line 12.2-12.29'},
{'discriminator': (('x', 'blah'), ('y', 1)),
'includepath': ['tests/samplepackage/baro2.zcml',
'tests/samplepackage/bar1.zcml'],
'info': 'File "tests/samplepackage/bar1.zcml", line 5.2-5.24'},
{'discriminator': (('x', 'blah'), ('y', 0)),
'includepath': ['tests/samplepackage/baro2.zcml'],
'info': 'File "tests/samplepackage/bar21.zcml", line 3.2-3.24'},
{'discriminator': (('x', 'blah'), ('y', 2)),
'includepath': ['tests/samplepackage/baro2.zcml'],
'info': 'File "tests/samplepackage/bar2.zcml", line 5.2-5.24'},
{'discriminator': (('x', 'blah'), ('y', 1)),
'includepath': ['tests/samplepackage/baro2.zcml'],
'info': 'File "tests/samplepackage/bar2.zcml", line 6.2-6.24'}]
>>> context.execute_actions()
>>> len(foo.data)
3
>>> del foo.data[:]
"""
def test_XMLConfig():
"""Test processing a configuration file.
We'll use the same example from test_includeOverrides:
>>> here = os.path.dirname(__file__)
>>> path = os.path.join(here, "samplepackage", "baro.zcml")
First, process the configuration file:
>>> x = xmlconfig.XMLConfig(path)
Second, call the resulting object to process the actions:
>>> x()
And verify the data as above:
>>> len(foo.data)
3
>>> data = foo.data.pop(0)
>>> data.args
(('x', 'blah'), ('y', 0))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/bar21.zcml", line 3.2-3.24
>>> data = foo.data.pop(0)
>>> data.args
(('x', 'blah'), ('y', 2))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/bar2.zcml", line 5.2-5.24
>>> data = foo.data.pop(0)
>>> data.args
(('x', 'blah'), ('y', 1))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/bar2.zcml", line 6.2-6.24
Finally, clean up.
>>> from zope.testing.cleanup import CleanUp
>>> CleanUp().cleanUp()
"""
def test_XMLConfig_w_module():
"""Test processing a configuration file for a module.
We'll use the same example from test_includeOverrides:
>>> import zope.configuration.tests.samplepackage as module
First, process the configuration file:
>>> x = xmlconfig.XMLConfig("baro.zcml", module)
Second, call the resulting object to process the actions:
>>> x()
And verify the data as above:
>>> len(foo.data)
3
>>> data = foo.data.pop(0)
>>> data.args
(('x', 'blah'), ('y', 0))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/bar21.zcml", line 3.2-3.24
>>> data = foo.data.pop(0)
>>> data.args
(('x', 'blah'), ('y', 2))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/bar2.zcml", line 5.2-5.24
>>> data = foo.data.pop(0)
>>> data.args
(('x', 'blah'), ('y', 1))
>>> print clean_info_path(`data.info`)
File "tests/samplepackage/bar2.zcml", line 6.2-6.24
Finally, clean up.
>>> from zope.testing.cleanup import CleanUp
>>> CleanUp().cleanUp()
"""
def test_suite():
return unittest.TestSuite((
DocTestSuite('zope.configuration.xmlconfig'),
DocTestSuite(),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')