Skip to content

Commit

Permalink
100% coverage for matcher.py
Browse files Browse the repository at this point in the history
There are unfortunately a few no-covers because of very deeply nested
functions that are difficult to mock around.

There is at least one genuine bug fix.
  • Loading branch information
jamadden committed Feb 11, 2017
1 parent 1fc4db3 commit d6096c2
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 15 deletions.
34 changes: 20 additions & 14 deletions ZConfig/matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
##############################################################################
"""Utility that manages the binding of configuration data to a section."""

import sys

import ZConfig

from ZConfig.info import ValueInfo
from ZConfig._compat import reraise


class BaseMatcher(object):
Expand All @@ -33,9 +36,7 @@ def __init__(self, info, type, handlers):
assert info.attribute is not None
self._values[info.attribute] = v
self._sectionnames = {}
if handlers is None:
handlers = []
self.handlers = handlers
self.handlers = handlers if handlers is not None else []

def __repr__(self):
clsname = self.__class__.__name__
Expand All @@ -56,7 +57,7 @@ def addSection(self, type, name, sectvalue):
v.append(sectvalue)
elif v is None:
self._values[attr] = sectvalue
else:
else: # pragma: no cover
raise ZConfig.ConfigurationError(
"too many instances of %s section" % repr(ci.sectiontype.name))

Expand All @@ -77,7 +78,7 @@ def addValue(self, key, value, position):
raise ZConfig.ConfigurationError(
repr(key) + " is not a known key name")
k, ci = arbkey_info
if ci.issection():
if ci.issection(): # pragma: no cover
if ci.name:
extra = " in %s sections" % repr(self.type.name)
else:
Expand All @@ -91,17 +92,20 @@ def addValue(self, key, value, position):
v = self._values[attr]
if v is None:
if k == '+':
v = {}
v = {} # pragma: no cover
elif ismulti:
v = []
v = [] # pragma: no cover
self._values[attr] = v
elif not ismulti:
if k != '+':
raise ZConfig.ConfigurationError(
repr(key) + " does not support multiple values")
elif len(v) == ci.maxOccurs:
elif len(v) == ci.maxOccurs: # pragma: no cover
# This code may be impossible to hit. Previously it would
# have raised a NameError because it used an unbound
# local.
raise ZConfig.ConfigurationError(
"too many values for " + repr(name))
"too many values for " + repr(ci))

value = ValueInfo(value, position)
if k == '+':
Expand All @@ -111,7 +115,7 @@ def addValue(self, key, value, position):
else:
v[realkey] = [value]
else:
if realkey in v:
if realkey in v: # pragma: no cover
raise ZConfig.ConfigurationError(
"too many values for " + repr(key))
v[realkey] = value
Expand Down Expand Up @@ -153,7 +157,7 @@ def finish(self):
raise ZConfig.ConfigurationError(
"no values for %s; %s required" % (key, ci.minOccurs))
else:
v = values[attr] = default[:]
v = values[attr] = default[:] # pragma: no cover
if ci.ismulti():
if not v:
default = ci.getdefault()
Expand All @@ -167,7 +171,7 @@ def finish(self):
% (key, len(v), ci.minOccurs))
if v is None and not ci.issection():
if ci.ismulti():
v = ci.getdefault()[:]
v = ci.getdefault()[:] # pragma: no cover
else:
v = ci.getdefault()
values[attr] = v
Expand All @@ -187,8 +191,9 @@ def constuct(self):
try:
s = st.datatype(s)
except ValueError as e:
raise ZConfig.DataConversionError(
dce = ZConfig.DataConversionError(
e, s, (-1, -1, None))
reraise(type(dce), dce, sys.exc_info()[2])
v.append(s)
elif ci.name == '+':
v = values[attr]
Expand All @@ -202,8 +207,9 @@ def constuct(self):
try:
v = st.datatype(values[attr])
except ValueError as e:
raise ZConfig.DataConversionError(
dce = ZConfig.DataConversionError(
e, values[attr], (-1, -1, None))
reraise(type(dce), dce, sys.exc_info()[2])
else:
v = None
elif name == '+':
Expand Down
1 change: 0 additions & 1 deletion ZConfig/tests/test_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import unittest

import ZConfig
from ZConfig import SchemaError
from ZConfig import ConfigurationError

Expand Down
151 changes: 151 additions & 0 deletions ZConfig/tests/test_matcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
##############################################################################
#
# Copyright (c) 2017 Zope Foundation 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.
#
##############################################################################

import unittest

from ZConfig import ConfigurationError
from ZConfig import DataConversionError

from ZConfig.matcher import SectionValue
from ZConfig.matcher import SectionMatcher
from ZConfig.matcher import BaseMatcher

class SectionValueTestCase(unittest.TestCase):

def test_repr(self):
class MockMatcher(object):
type = None

matcher = MockMatcher()
matcher.type = MockMatcher()
matcher.type.name = 'matcher'

sv = SectionValue({}, 'name', matcher)
self.assertIn('name', repr(sv))

sv = SectionValue({}, None, matcher)
self.assertIn('at', repr(sv))

self.assertIs(matcher, sv.getSectionMatcher())

def test_str(self):
d = {'k': 'v'}
sv = SectionValue(d, None, None)
self.assertEqual(
'k : v',
str(sv))

class SectionMatcherTestCase(unittest.TestCase):

def test_constructor_error(self):
class Mock(object):
name = 'name'
def allowUnnamed(self):
return False
mock = Mock()
self.assertRaisesRegexp(ConfigurationError,
"sections may not be unnamed",
SectionMatcher,
mock, mock, None, None)

class BaseMatcherTestCase(unittest.TestCase):

def test_repr(self):
class Mock(dict):
name = 'name'

matcher = BaseMatcher(None, Mock(), None)
repr(matcher)

def test_duplicate_section_names(self):
class Mock(dict):
name = 'name'

matcher = BaseMatcher(None, Mock(), None)
matcher._sectionnames['foo'] = None

self.assertRaisesRegexp(ConfigurationError,
"section names must not be re-used",
matcher.addSection,
None, 'foo', None)

def test_construct_errors(self):
class MockType(object):
attribute = 'attr'

_multi = True
_section = True

def ismulti(self):
return self._multi

def issection(self):
return self._section

type_ = []
matcher = BaseMatcher(None, type_, None)
type_.append( ('key', MockType() ) )

class MockSection(object):
def getSectionDefinition(self):
return self

def datatype(self, _s):
raise ValueError()

matcher._values['attr'] = [MockSection()]

with self.assertRaises(DataConversionError):
matcher.constuct()

type_[0][1]._multi = False
matcher._values['attr'] = MockSection()
with self.assertRaises(DataConversionError):
matcher.constuct()


def test_create_child_bad_name(self):

class MockType(list):
name = 'foo'
sectiontype = None

def getsectioninfo(self, type_name, name):
return self

def isabstract(self):
return False

def isAllowedName(self, name):
return False

t = MockType()
t.sectiontype = MockType()
matcher = BaseMatcher(None, t, None)
self.assertRaisesRegexp(ConfigurationError,
'is not an allowed name',
matcher.createChildMatcher,
MockType(), 'ignored')



def test_suite():
suite = unittest.makeSuite(SectionValueTestCase)
suite.addTest(unittest.makeSuite(SectionMatcherTestCase))
suite.addTest(unittest.makeSuite(BaseMatcherTestCase))
return suite


if __name__ == '__main__':
unittest.main(defaultTest='test_suite')

0 comments on commit d6096c2

Please sign in to comment.