Skip to content

Commit

Permalink
100% coverage for info.py
Browse files Browse the repository at this point in the history
- SectionType has a few no-covers for exception cases that I didn't
  feel like mocking up all the conditions for.

- Make the type objects, well, objects. They had a ``__metaclass__``
  statement that I think did the same thing on Python 2.
  • Loading branch information
jamadden committed Feb 11, 2017
1 parent e13cd13 commit 1fc4db3
Show file tree
Hide file tree
Showing 2 changed files with 249 additions and 25 deletions.
45 changes: 20 additions & 25 deletions ZConfig/info.py
Expand Up @@ -17,31 +17,23 @@
import ZConfig

from abc import abstractmethod
from functools import total_ordering

from ZConfig._compat import AbstractBaseClass

@total_ordering
class UnboundedThing(object):
__slots__ = ()

def __lt__(self, other):
return False

def __le__(self, other):
return isinstance(other, self.__class__)

def __gt__(self, other):
return True

def __ge__(self, other):
if isinstance(other, self.__class__):
return False
return True

def __eq__(self, other):
return isinstance(other, self.__class__)

def __ne__(self, other):
return not isinstance(other, self.__class__)

def __repr__(self):
def __repr__(self): # pragma: no cover
return "<Unbounded>"

Unbounded = UnboundedThing()
Expand All @@ -62,7 +54,7 @@ def convert(self, datatype):
raise ZConfig.DataConversionError(e, self.value, self.position)


class BaseInfo(AbstractBaseClass):
class BaseInfo(object):
"""Information about a single configuration key."""

description = None
Expand All @@ -71,13 +63,13 @@ class BaseInfo(AbstractBaseClass):

def __init__(self, name, datatype, minOccurs, maxOccurs, handler,
attribute):
if maxOccurs is not None and maxOccurs < 1:
if maxOccurs is not None:
if maxOccurs < 1:
raise ZConfig.SchemaError(
"maxOccurs must be at least 1")
if minOccurs is not None and minOccurs < maxOccurs:
if minOccurs is not None and minOccurs > maxOccurs:
raise ZConfig.SchemaError(
"minOccurs must be at least maxOccurs")
"minOccurs cannot be more than maxOccurs")
self.name = name
self.datatype = datatype
self.minOccurs = minOccurs
Expand All @@ -99,7 +91,7 @@ def issection(self):
return False


class BaseKeyInfo(BaseInfo):
class BaseKeyInfo(AbstractBaseClass, BaseInfo):

_rawdefaults = None

Expand Down Expand Up @@ -228,6 +220,8 @@ def __init__(self, name, sectiontype, minOccurs, maxOccurs, handler,
# handler - handler name called when value(s) must take effect,
# or None
# attribute - name of the attribute on the SectionValue object
assert maxOccurs is not None
# because we compare it to 1 which is a Py3 error
if maxOccurs > 1:
if name not in ('*', '+'):
raise ZConfig.SchemaError(
Expand Down Expand Up @@ -274,8 +268,8 @@ def getdefault(self):
return None


class AbstractType:
__metaclass__ = type
class AbstractType(object):

__slots__ = '_subtypes', 'name', 'description'

def __init__(self, name):
Expand Down Expand Up @@ -305,7 +299,7 @@ def isabstract(self):
return True


class SectionType:
class SectionType(object):
def __init__(self, name, keytype, valuetype, datatype, registry, types):
# name - name of the section, or '*' or '+'
# datatype - type for the section itself
Expand Down Expand Up @@ -398,7 +392,7 @@ def getsectioninfo(self, type, name):
if st.isabstract():
try:
st = st.getsubtype(type)
except ZConfig.ConfigurationError:
except ZConfig.ConfigurationError: # pragma: no cover
raise ZConfig.ConfigurationError(
"section type %s not allowed for name %s"
% (repr(type), repr(key)))
Expand All @@ -415,12 +409,12 @@ def getsectioninfo(self, type, name):
return info
elif info.sectiontype.isabstract():
st = info.sectiontype
if st.name == type:
if st.name == type: # pragma: no cover
raise ZConfig.ConfigurationError(
"cannot define section with an abstract type")
try:
st = st.getsubtype(type)
except ZConfig.ConfigurationError:
except ZConfig.ConfigurationError: # pragma: no cover
# not this one; maybe a different one
pass
else:
Expand Down Expand Up @@ -464,7 +458,8 @@ def getunusedtypes(self):
for n in reqtypes:
alltypes.remove(n)
if self.name and self.name in alltypes:
alltypes.remove(self.name)
# Not clear we can get here
alltypes.remove(self.name) # pragma: no cover.
return alltypes

def createSectionType(self, name, keytype, valuetype, datatype):
Expand Down
229 changes: 229 additions & 0 deletions ZConfig/tests/test_info.py
@@ -0,0 +1,229 @@
##############################################################################
#
# 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

import ZConfig
from ZConfig import SchemaError
from ZConfig import ConfigurationError

from ZConfig.info import Unbounded
from ZConfig.info import BaseInfo
from ZConfig.info import BaseKeyInfo
from ZConfig.info import KeyInfo
from ZConfig.info import SectionInfo
from ZConfig.info import AbstractType
from ZConfig.info import SectionType
from ZConfig.info import SchemaType

class UnboundTestCase(unittest.TestCase):

def test_order(self):
self.assertGreater(Unbounded, self)
self.assertFalse(Unbounded > Unbounded)
self.assertEqual(Unbounded, Unbounded)

class InfoMixin(object):

Class = None

default_kwargs = {'name': '', 'datatype': None, 'handler': None,
'minOccurs': None, 'maxOccurs': None, 'attribute': None}

def make_one(self, **kwargs):
args = self.default_kwargs.copy()
args.update(kwargs)
return self.Class(**args)


class BaseInfoTestCase(InfoMixin, unittest.TestCase):

Class = BaseInfo

def test_constructor_error(self):
self.assertRaisesRegexp(SchemaError,
'maxOccurs',
self.make_one,
maxOccurs=0)

# This case doesn't really make sense
self.assertRaisesRegexp(SchemaError,
'minOccurs',
self.make_one,
maxOccurs=1,
minOccurs=2)

def test_repr(self):
# just doesn't raise
repr(self.make_one())

class BaseKeyInfoTestCase(InfoMixin, unittest.TestCase):

class Class(BaseKeyInfo):
def add_valueinfo(self, vi, key):
pass

def test_cant_instantiate(self):
self.Class = BaseKeyInfo
with self.assertRaises(TypeError):
self.make_one()
del self.Class

def test_finish(self):
info = self.make_one(minOccurs=1)
info.finish()
with self.assertRaises(SchemaError):
info.finish()

def test_adddefaultc(self):
info = self.make_one(name='foo', minOccurs=1)
self.assertRaisesRegexp(SchemaError,
'unexpected key for default',
info.adddefault,
None, None, key='key')

class KeyInfoTestCase(InfoMixin, unittest.TestCase):

Class = KeyInfo
default_kwargs = InfoMixin.default_kwargs.copy()
default_kwargs.pop('maxOccurs')

def test_add_with_default(self):
info = self.make_one(minOccurs=1, name='name')
info.adddefault('value', None)
self.assertRaisesRegexp(SchemaError,
'cannot set more than one',
info.adddefault,
'value', None)

class SectionInfoTestCase(InfoMixin, unittest.TestCase):

Class = SectionInfo

class MockSectionType(object):
name = None
@classmethod
def isabstract(cls):
return True

default_kwargs = InfoMixin.default_kwargs.copy()
default_kwargs.pop('datatype')
default_kwargs['sectiontype'] = MockSectionType

def test_constructor_error(self):
self.assertRaisesRegexp(SchemaError,
'must use a name',
self.make_one,
name='name', maxOccurs=2)
self.assertRaisesRegexp(SchemaError,
'must specify a target attribute',
self.make_one,
name='*', maxOccurs=2)

def test_misc(self):
info = self.make_one(maxOccurs=1)
repr(info)
self.assertFalse(info.isAllowedName('*'))
self.assertFalse(info.isAllowedName('+'))

class AbstractTypeTestCase(unittest.TestCase):

def test_subtypes(self):

t = AbstractType('name')
self.assertFalse(t.hassubtype('foo'))
self.assertEqual([], list(t.getsubtypenames()))

self.name = 'foo'
t.addsubtype(self)
self.assertTrue(t.hassubtype('foo'))

class SectionTypeTestCase(unittest.TestCase):

def make_one(self, name='', keytype=None, valuetype=None,
datatype=None, registry={}, types=None):
return SectionType(name, keytype, valuetype, datatype, registry, types)

def test_getinfo_no_key(self):
info = self.make_one()
self.assertRaisesRegexp(ConfigurationError,
"cannot match a key without a name",
info.getinfo,
None)

def test_required_types_with_name(self):
info = self.make_one(name='foo')
self.assertEqual(['foo'], info.getrequiredtypes())

def test_getsectioninfo(self):
class MockChild(object):
_issection = False
def issection(self):
return self._issection
child = MockChild()

info = self.make_one()

info._children.append(('foo', child))

self.assertRaisesRegexp(ConfigurationError,
'already in use for key',
info.getsectioninfo,
None, 'foo')

self.assertRaisesRegexp(ConfigurationError,
'no matching section',
info.getsectioninfo,
None, 'baz')

class SchemaTypeTestCase(unittest.TestCase):

def test_various(self):
class Mock(object):
pass

mock = Mock()
schema = SchemaType(None, None, None, None, 'url', {})

mock.name = 'name'
schema.addtype(mock)
with self.assertRaises(SchemaError):
schema.addtype(mock)

self.assertTrue(schema.allowUnnamed())
self.assertFalse(schema.isAllowedName(None))

with self.assertRaises(SchemaError):
schema.deriveSectionType(schema, None, None, None, None)

schema.addComponent('name')
self.assertRaisesRegexp(SchemaError,
'already have component',
schema.addComponent,
'name')

def test_suite():
suite = unittest.makeSuite(UnboundTestCase)
suite.addTest(unittest.makeSuite(BaseInfoTestCase))
suite.addTest(unittest.makeSuite(BaseKeyInfoTestCase))
suite.addTest(unittest.makeSuite(KeyInfoTestCase))
suite.addTest(unittest.makeSuite(SectionInfoTestCase))
suite.addTest(unittest.makeSuite(AbstractTypeTestCase))
suite.addTest(unittest.makeSuite(SectionTypeTestCase))
suite.addTest(unittest.makeSuite(SchemaTypeTestCase))
return suite


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

0 comments on commit 1fc4db3

Please sign in to comment.