195 lines
7.8 KiB
Python
195 lines
7.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
from nose import tools
|
|
|
|
import os
|
|
import types
|
|
import warnings
|
|
from kitchen.pycompat24.sets import add_builtin_set
|
|
add_builtin_set()
|
|
|
|
def logit(msg):
|
|
log = open('/var/tmp/test.log', 'a')
|
|
log.write('%s\n' % msg)
|
|
log.close()
|
|
|
|
class NoAll(RuntimeError):
|
|
pass
|
|
|
|
class FailedImport(RuntimeError):
|
|
pass
|
|
|
|
class Test__all__(object):
|
|
'''Test that every function in __all__ exists and that no public methods
|
|
are missing from __all__
|
|
'''
|
|
known_private = set([('kitchen', 'collections', 'version_tuple_to_string'),
|
|
('kitchen.collections', 'strictdict', 'defaultdict'),
|
|
('kitchen', 'i18n', 'version_tuple_to_string'),
|
|
('kitchen', 'i18n', 'to_bytes'),
|
|
('kitchen', 'i18n', 'to_unicode'),
|
|
('kitchen', 'i18n', 'ENOENT'),
|
|
('kitchen', 'i18n', 'byte_string_valid_encoding'),
|
|
('kitchen', 'i18n', 'isbasestring'),
|
|
('kitchen', 'i18n', 'partial'),
|
|
('kitchen', 'iterutils', 'isbasestring'),
|
|
('kitchen', 'iterutils', 'version_tuple_to_string'),
|
|
('kitchen', 'pycompat24', 'version_tuple_to_string'),
|
|
('kitchen', 'pycompat25', 'version_tuple_to_string'),
|
|
('kitchen.pycompat25.collections', '_defaultdict', 'b_'),
|
|
('kitchen', 'pycompat27', 'version_tuple_to_string'),
|
|
('kitchen.pycompat27', 'subprocess', 'MAXFD'),
|
|
('kitchen.pycompat27', 'subprocess', 'list2cmdline'),
|
|
('kitchen.pycompat27', 'subprocess', 'mswindows'),
|
|
('kitchen', 'text', 'version_tuple_to_string'),
|
|
('kitchen.text', 'converters', 'b_'),
|
|
('kitchen.text', 'converters', 'b64decode'),
|
|
('kitchen.text', 'converters', 'b64encode'),
|
|
('kitchen.text', 'converters', 'ControlCharError'),
|
|
('kitchen.text', 'converters', 'guess_encoding'),
|
|
('kitchen.text', 'converters', 'html_entities_unescape'),
|
|
('kitchen.text', 'converters', 'isbytestring'),
|
|
('kitchen.text', 'converters', 'isunicodestring'),
|
|
('kitchen.text', 'converters', 'process_control_chars'),
|
|
('kitchen.text', 'converters', 'XmlEncodeError'),
|
|
('kitchen.text', 'misc', 'b_'),
|
|
('kitchen.text', 'misc', 'chardet'),
|
|
('kitchen.text', 'misc', 'ControlCharError'),
|
|
('kitchen.text', 'display', 'b_'),
|
|
('kitchen.text', 'display', 'ControlCharError'),
|
|
('kitchen.text', 'display', 'to_bytes'),
|
|
('kitchen.text', 'display', 'to_unicode'),
|
|
('kitchen.text', 'utf8', 'b_'),
|
|
('kitchen.text', 'utf8', 'byte_string_textual_width_fill'),
|
|
('kitchen.text', 'utf8', 'byte_string_valid_encoding'),
|
|
('kitchen.text', 'utf8', 'fill'),
|
|
('kitchen.text', 'utf8', 'isunicodestring'),
|
|
('kitchen.text', 'utf8', 'textual_width'),
|
|
('kitchen.text', 'utf8', 'textual_width_chop'),
|
|
('kitchen.text', 'utf8', 'to_bytes'),
|
|
('kitchen.text', 'utf8', 'to_unicode'),
|
|
('kitchen.text', 'utf8', 'wrap'),
|
|
])
|
|
lib_dir = os.path.join(os.path.dirname(__file__), '..', 'kitchen')
|
|
|
|
def setUp(self):
|
|
# Silence deprecation warnings
|
|
warnings.simplefilter('ignore', DeprecationWarning)
|
|
def tearDown(self):
|
|
warnings.simplefilter('default', DeprecationWarning)
|
|
|
|
def walk_modules(self, basedir, modpath):
|
|
files = os.listdir(basedir)
|
|
files.sort()
|
|
for fn in files:
|
|
path = os.path.join(basedir, fn)
|
|
if os.path.isdir(path):
|
|
pkg_init = os.path.join(path, '__init__.py')
|
|
if os.path.exists(pkg_init):
|
|
yield pkg_init, modpath + fn
|
|
for p, m in self.walk_modules(path, modpath + fn + '.'):
|
|
yield p, m
|
|
continue
|
|
if not fn.endswith('.py') or fn == '__init__.py':
|
|
continue
|
|
yield path, modpath + fn[:-3]
|
|
|
|
def check_has__all__(self, modpath):
|
|
# This heuristic speeds up the process by removing, de facto,
|
|
# most test modules (and avoiding the auto-executing ones).
|
|
f = None
|
|
try:
|
|
try:
|
|
f = open(modpath, 'r', encoding='utf-8')
|
|
tools.ok_('__all__' in f.read(), '%s does not contain __all__' % modpath)
|
|
except IOError as e:
|
|
tools.ok_(False, '%s' % e)
|
|
finally:
|
|
if f:
|
|
f.close()
|
|
|
|
def test_has__all__(self):
|
|
'''
|
|
For each module, check that it has an __all__
|
|
'''
|
|
# Blacklisted modules and packages
|
|
blacklist = set([ ])
|
|
|
|
for path, modname in [m for m in self.walk_modules(self.lib_dir, '')
|
|
if m[1] not in blacklist]:
|
|
# Check that it has an __all__
|
|
yield self.check_has__all__, path
|
|
|
|
def check_everything_in__all__exists(self, modname, modpath):
|
|
names = {}
|
|
exec('from %s import %s' % (modpath, modname), names)
|
|
if not hasattr(names[modname], '__all__'):
|
|
# This should have been reported by test_has__all__
|
|
return
|
|
|
|
interior_names = {}
|
|
try:
|
|
exec('from %s.%s import *' % (modpath, modname), interior_names)
|
|
except Exception as e:
|
|
# Include the module name in the exception string
|
|
tools.ok_(False, '__all__ failure in %s: %s: %s' % (
|
|
modname, e.__class__.__name__, e))
|
|
if '__builtins__' in interior_names:
|
|
del interior_names['__builtins__']
|
|
keys = set(interior_names)
|
|
all = set(names[modname].__all__)
|
|
tools.ok_(keys == all)
|
|
|
|
def test_everything_in__all__exists(self):
|
|
'''
|
|
For each name in module's __all__, check that it exists
|
|
'''
|
|
# Blacklisted modules and packages
|
|
blacklist = set([ ])
|
|
|
|
for path, modname in [m for m in self.walk_modules(self.lib_dir, '')
|
|
if m[1] not in blacklist]:
|
|
# From path, deduce the module name
|
|
from_name = path[path.find('../kitchen') + 3:]
|
|
if from_name.endswith('__init__.py'):
|
|
# Remove __init__.py as well as the filename
|
|
from_name = os.path.dirname(from_name)
|
|
from_name = os.path.dirname(from_name)
|
|
from_name = from_name.translate({ord('/'): '.'})
|
|
yield self.check_everything_in__all__exists, modname.split('.')[-1], from_name
|
|
|
|
|
|
def check__all__is_complete(self, modname, modpath):
|
|
names = {}
|
|
exec('from %s import %s' % (modpath, modname), names)
|
|
if not hasattr(names[modname], '__all__'):
|
|
# This should have been reported by test_has__all__
|
|
return
|
|
|
|
mod = names[modname]
|
|
expected_public = [k for k in mod.__dict__ if (modpath, modname, k)
|
|
not in self.known_private and not k.startswith("_") and not
|
|
isinstance(mod.__dict__[k], types.ModuleType)]
|
|
|
|
all = set(mod.__all__)
|
|
public = set(expected_public)
|
|
tools.ok_(all.issuperset(public), 'These public names are not in %s.__all__: %s'
|
|
% (modname, ', '.join(public.difference(all))))
|
|
|
|
def test__all__is_complete(self):
|
|
'''
|
|
For each module, check that every public name is in __all__
|
|
'''
|
|
# Blacklisted modules and packages
|
|
blacklist = set(['pycompat27.subprocess._subprocess',
|
|
'pycompat24.base64._base64'])
|
|
|
|
for path, modname in [m for m in self.walk_modules(self.lib_dir, '')
|
|
if m[1] not in blacklist]:
|
|
# From path, deduce the module name
|
|
from_name = path[path.find('../kitchen') + 3:]
|
|
if from_name.endswith('__init__.py'):
|
|
# Remove __init__.py as well as the filename
|
|
from_name = os.path.dirname(from_name)
|
|
from_name = os.path.dirname(from_name)
|
|
from_name = from_name.translate({ord('/'): '.'})
|
|
yield self.check__all__is_complete, modname.split('.')[-1], from_name
|