Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ language: python
python:
- "2.6"
- "2.7"
script: python setup.py test
- "3.6"
script:
- python setup.py test

6 changes: 5 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
2.0.0
0.3.0

* Python 3 support

0.2.0

* mandrel-runner uses return value of callable as the exit code.

Expand Down
4 changes: 2 additions & 2 deletions mandrel/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def find_logging_configuration():
"""
for path in util.find_files(LOGGING_CONFIG_BASENAME, SEARCH_PATHS, matches=1):
return path
raise exception.UnknownConfigurationException, "Cannot find logging configuration file(s) '%s'" % LOGGING_CONFIG_BASENAME
raise exception.UnknownConfigurationException("Cannot find logging configuration file(s) '%s'" % LOGGING_CONFIG_BASENAME)

DEFAULT_LOGGING_CALLBACK = initialize_simple_logging
DISABLE_EXISTING_LOGGERS = True
Expand Down Expand Up @@ -109,7 +109,7 @@ def _find_bootstrap_base():
while not os.path.isfile(os.path.join(current, __BOOTSTRAP_BASENAME)):
parent = os.path.dirname(current)
if parent == current:
raise exception.MissingBootstrapException, 'Cannot find %s file in directory hierarchy' % __BOOTSTRAP_BASENAME
raise exception.MissingBootstrapException('Cannot find %s file in directory hierarchy' % __BOOTSTRAP_BASENAME)
current = parent

return current, os.path.join(current, __BOOTSTRAP_BASENAME)
Expand Down
6 changes: 3 additions & 3 deletions mandrel/config/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def find_configuration_file(name):
"""
for path in find_configuration_files(name):
return path
raise exception.UnknownConfigurationException, "No configuration file found for name '%s'" % name
raise exception.UnknownConfigurationException("No configuration file found for name '%s'" % name)

def get_loader(path):
"""Gets the configuration loader for path according to file extension.
Expand All @@ -98,7 +98,7 @@ def get_loader(path):
fullext = '.' + ext
if path[-len(fullext):] == fullext:
return loader
raise exception.UnknownConfigurationException, "No configuration loader found for path '%s'" % path
raise exception.UnknownConfigurationException("No configuration loader found for path '%s'" % path)

def load_configuration_file(path):
"""Loads the configuration at path and returns it.
Expand Down Expand Up @@ -267,7 +267,7 @@ def chained_get(self, attribute):
except AttributeError:
pass

raise AttributeError, 'No such attribute: %s' % attribute
raise AttributeError('No such attribute: %s' % attribute)

def __getattr__(self, attr):
return self.chained_get(attr)
Expand Down
7 changes: 6 additions & 1 deletion mandrel/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,12 @@ def prepare_environment(self, args):
def execute_script(self, script):
glb = globals()
glb.update(__name__='__main__', __file__=script)
return execfile(script, glb)
if sys.version_info[0] > 2:
import builtins
execf = getattr(builtins, 'exec')
return execf(compile(open(script).read(), script, 'exec'), glb)
else:
return execfile(script, glb)

def execute(self, target, args):
self.prepare_environment(args)
Expand Down
11 changes: 6 additions & 5 deletions mandrel/test/config/configuration_class_test.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import unittest
import mock

import mandrel.config
from mandrel.test import utils
from mandrel import exception
from mandrel.test import utils


class TestConfigurationClass(utils.TestCase):
@mock.patch('mandrel.config.core.get_configuration')
Expand All @@ -21,7 +22,7 @@ def testGetLoggerName(self):
result = mandrel.config.core.Configuration.get_logger_name('some.nested.name')
self.assertEqual(mock_name + '.some.nested.name', result)

@mock.patch('mandrel.bootstrap')
@mock.patch('mandrel.bootstrap', create=True)
@mock.patch('mandrel.config.core.Configuration.get_logger_name')
def testGetLogger(self, get_logger_name, bootstrap):
mock_name = str(mock.Mock(name='AnotherMockConfigurationName'))
Expand All @@ -45,7 +46,7 @@ def testBasicAttributes(self):
self.assertEqual(config, c.configuration)
self.assertEqual((), c.chain)

chain = tuple(mock.Mock(name='Chain%d' % x) for x in xrange(3))
chain = tuple(mock.Mock(name='Chain%d' % x) for x in range(3))
c = mandrel.config.core.Configuration(config, *chain)
self.assertEqual(config, c.configuration)
self.assertEqual(chain, c.chain)
Expand Down Expand Up @@ -136,7 +137,7 @@ def testGetConfiguration(self, loader):
self.assertEqual(loader.return_value, c.configuration)
self.assertEqual((), c.chain)

chain = tuple(mock.Mock() for x in xrange(5))
chain = tuple(mock.Mock() for x in range(5))
c = mandrel.config.core.Configuration.get_configuration(*chain)
self.assertEqual(loader.return_value, c.configuration)
self.assertEqual(chain, c.chain)
Expand Down
25 changes: 16 additions & 9 deletions mandrel/test/config/loader_functionality_test.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import contextlib
import unittest

import mock

import mandrel
from mandrel import exception
from mandrel.test import utils

try:
# python 3 compatibility
from importlib import reload
except ImportError:
pass


def scenario(func):
def wrapper(*a, **kw):
with mock.patch('mandrel.bootstrap') as bootstrap:
with mock.patch('mandrel.bootstrap', create=True) as bootstrap:
if hasattr(mandrel, 'config'):
reload(mandrel.config.core)
reload(mandrel.config)
Expand Down Expand Up @@ -48,7 +55,7 @@ def testGetPossibleBasenames(self):
def testFindConfigurationFiles(self):
with mock.patch('mandrel.util.find_files') as find_files:
with mock.patch('mandrel.config.core.get_possible_basenames') as get_possible_basenames:
exts = [mock.Mock(name='Extension%d' % x) for x in xrange(3)]
exts = [mock.Mock(name='Extension%d' % x) for x in range(3)]
mandrel.config.core.LOADERS = [(ext, mock.Mock(name='Reader')) for ext in exts]
mandrel.bootstrap.SEARCH_PATHS = mock.Mock()
name = mock.Mock(name='FileBase')
Expand All @@ -60,7 +67,7 @@ def testFindConfigurationFiles(self):
@scenario
def testFindConfigurationFileWithMatch(self):
with mock.patch('mandrel.config.core.find_configuration_files') as find_configuration_files:
paths = [mock.Mock(name='Path%d' % x) for x in xrange(10)]
paths = [mock.Mock(name='Path%d' % x) for x in range(10)]
find_configuration_files.side_effect = lambda x: iter(paths)
name = mock.Mock(name='SomeBase')
result = mandrel.config.core.find_configuration_file(name)
Expand All @@ -76,11 +83,11 @@ def testFindConfigurationFilesWithoutMatch(self):

@scenario
def testGetLoader(self):
exts = [str(mock.Mock(name='Extension%d' % x)) for x in xrange(3)]
loaders = [mock.Mock(name='Loader%d' % x) for x in xrange(len(exts))]
mandrel.config.core.LOADERS = zip(exts, loaders)
exts = [str(mock.Mock(name='Extension%d' % x)) for x in range(3)]
loaders = [mock.Mock(name='Loader%d' % x) for x in range(len(exts))]
mandrel.config.core.LOADERS = list(zip(exts, loaders))

for i in xrange(len(exts)):
for i in range(len(exts)):
ext, loader = mandrel.config.core.LOADERS[i]
path = '%s.%s' % (mock.Mock(name='someFile'), ext)
result = mandrel.config.core.get_loader(path)
Expand Down
20 changes: 13 additions & 7 deletions mandrel/test/runner_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ def func(*a, **kw):

KNOWN_PATH = os.path.realpath('')

if sys.version_info[0] > 2:
builtin_name = 'builtins'
else:
builtin_name = '__builtin__'


class TestRunner(unittest.TestCase):
@scenario('-s', 'foo:bar:bah:', 'gloof', 'glof', 'floo', ensure_target=False)
def testSearchPathSet(self, path):
Expand Down Expand Up @@ -74,7 +80,7 @@ def testLogConfigBasenamePath(self, path):
@mock.patch.object(runner.AbstractRunner, 'process_options')
@mock.patch.object(runner.AbstractRunner, 'execute')
def testOrderOfOperations(self, path, execute, process_options):
mocks = [mock.Mock('positional%d' % x) for x in xrange(4)]
mocks = [mock.Mock('positional%d' % x) for x in range(4)]
target = mocks.pop(0)
process_options.return_value = (target, mocks)
runner.AbstractRunner().run()
Expand All @@ -90,7 +96,7 @@ def testNotImplemented(self, path, process_options):
@scenario()
def testImporting(self, _):
obj = runner.AbstractRunner()
with mock.patch('__builtin__.__import__') as importer:
with mock.patch(builtin_name + '.__import__') as importer:
self.assertEqual(importer.return_value.bootstrap, obj.bootstrapper)
importer.assert_called_once_with('mandrel.bootstrap')

Expand All @@ -102,26 +108,26 @@ def testLaunch(self, run):
@mock.patch('mandrel.util.get_by_fqn')
def testCallableRunner(self, get_by_fqn):
target = str(mock.Mock(name='MockTarget'))
opts = [str(mock.Mock(name='Arg%d' % x)) for x in xrange(3)]
opts = [str(mock.Mock(name='Arg%d' % x)) for x in range(3)]
result = runner.CallableRunner().execute(target, opts)
get_by_fqn.assert_called_once_with(target)
get_by_fqn.return_value.assert_called_once_with(opts)
self.assertEqual(get_by_fqn.return_value.return_value, result)

@mock.patch('__builtin__.globals')
@mock.patch(builtin_name + '.globals')
@mock.patch('sys.argv', new=['foo', 'bar', 'bah'])
@mock.patch('__builtin__.execfile')
@mock.patch('mandrel.runner.ScriptRunner.execute_script')
def testScriptRunner(self, mock_exec, mock_globals):
target = str(mock.Mock(name='MockTarget'))
opts = [str(mock.Mock(name='Arg%d' % x)) for x in xrange(3)]
opts = [str(mock.Mock(name='Arg%d' % x)) for x in range(3)]
glb = {'foo': mock.Mock(), 'bar': mock.Mock(), '__file__': mock.Mock(), '__name__': mock.Mock()}
mock_globals.side_effect = lambda: dict(glb)
exp = dict(glb)
exp['__file__'] = target
exp['__name__'] = '__main__'

result = runner.ScriptRunner().execute(target, opts)
mock_exec.assert_called_once_with(target, exp)
mock_exec.assert_called_once_with(target)
self.assertEqual(mock_exec.return_value, result)
# Should add args at sys.argv[1:]
self.assertEqual(['foo'] + opts, sys.argv)
Expand Down
8 changes: 4 additions & 4 deletions mandrel/test/util/file_finder_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def scenario(**files_to_levels):
levels.append(b)
with utils.tempdir() as c:
levels.append(c)
for name, dirs in files_to_levels.items():
for name, dirs in list(files_to_levels.items()):
for level in dirs:
with open(os.path.join(levels[level], name), 'w') as f:
f.write(str(level))
Expand All @@ -32,21 +32,21 @@ def get_level(path):
class TestFileFinder(unittest.TestCase):
def testSingleFindOneMatch(self):
with scenario(**{'a.txt': (0, 1, 2), 'b.foo': (1, 2), 'c.bar': (2,)}) as dirs:
for name, level in {'a.txt': 0, 'b.foo': 1, 'c.bar': 2}.items():
for name, level in list({'a.txt': 0, 'b.foo': 1, 'c.bar': 2}.items()):
result = tuple(util.find_files(name, dirs, matches=1))
self.assertEqual(1, len(result))
self.assertEqual(level, get_level(result[0]))

def testSingleFindTwoMatch(self):
with scenario(**{'0.x': (0,), 'a.txt': (0, 1, 2), 'b.foo': (1, 2), 'c.bar': (2,)}) as dirs:
for name, levels in {'0.x': (0,), 'a.txt': (0, 1), 'b.foo': (1, 2), 'c.bar': (2,)}.items():
for name, levels in list({'0.x': (0,), 'a.txt': (0, 1), 'b.foo': (1, 2), 'c.bar': (2,)}.items()):
got = tuple(get_level(r) for r in util.find_files(name, dirs, matches=2))
self.assertEqual(levels, got)

def testSingleFindMultiMatch(self):
mapping = {'0.x': (0,), 'a.txt': (0, 1), 'b.blah': (0, 1, 2), 'c.pork': (1, 2), 'd.plonk': (1,), 'e.sporks': (2,)}
with scenario(**mapping) as dirs:
for name, levels in mapping.items():
for name, levels in list(mapping.items()):
got = tuple(get_level(r) for r in util.find_files(name, dirs))
self.assertEqual(levels, got)

Expand Down
2 changes: 1 addition & 1 deletion mandrel/test/util/loaders_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_harness_loader_straight(self):
callback = mock.Mock(name='Callback')
harness = util.harness_loader(loader)(callback)
name = mock.Mock(name='Plugin')
args = [mock.Mock(name='Arg%d' % x) for x in xrange(3)]
args = [mock.Mock(name='Arg%d' % x) for x in range(3)]

result = harness(name)
loader.assert_called_once_with(name)
Expand Down
12 changes: 6 additions & 6 deletions mandrel/test/util/transforming_list_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
_trans_count = itertools.count(0)

def mock_transform():
t = mock.Mock(name='MockTransform%d' % _trans_count.next())
t = mock.Mock(name='MockTransform%d' % next(_trans_count))
t.side_effect = lambda v: v.transformed
return t

_vals_count = itertools.count(0)

def mock_value():
return mock.Mock(name='MockValue%d' % _vals_count.next())
return mock.Mock(name='MockValue%d' % next(_vals_count))

class TestTransformingList(unittest.TestCase):
def testAppendBasics(self):
Expand All @@ -33,7 +33,7 @@ def testAppendBasics(self):
def testAccessBasics(self):
t = mock_transform()
l = util.TransformingList(t)
vals = [mock_value() for i in xrange(5)]
vals = [mock_value() for i in range(5)]
l[0:3] = vals[0:3]
self.assertEqual(3, len(l))
self.assertEqual(tuple(v.transformed for v in vals[0:3]), tuple(l))
Expand All @@ -51,7 +51,7 @@ def testAccessBasics(self):

def testExtend(self):
t = mock_transform()
vals = [mock_value() for i in xrange(5)]
vals = [mock_value() for i in range(5)]
exp = tuple(v.transformed for v in vals)
l = util.TransformingList(t)
l.extend(vals)
Expand All @@ -64,7 +64,7 @@ def testExtend(self):

def testInsertPop(self):
t = mock_transform()
a, b, c = (mock_value() for i in xrange(3))
a, b, c = (mock_value() for i in range(3))
l = util.TransformingList(t)
l.append(a)
l.append(b)
Expand All @@ -77,7 +77,7 @@ def testInsertPop(self):

def testContainment(self):
t = mock_transform()
vals = [mock_value() for i in xrange(5)]
vals = [mock_value() for i in range(5)]
l = util.TransformingList(t)
member = lambda v: v in l
self.assertFalse(member(vals[0]))
Expand Down
6 changes: 6 additions & 0 deletions mandrel/test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
import mandrel
import unittest

try:
# python 3 compatibility
from importlib import reload
except ImportError:
pass

class TestCase(unittest.TestCase):
def assertIs(self, a, b):
# python 2.6/2.7 compatibility
Expand Down
19 changes: 15 additions & 4 deletions mandrel/util.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import os
import re
import sys


if sys.version_info[0] == 2:
string_type = basestring
else:
string_type = str


class TransformingList(object):
__slots__ = ('_list', '_transformer')
Expand All @@ -9,7 +17,10 @@ def __init__(self, transformer):
self._transformer = transformer

def __setitem__(self, i, y):
self._list[i] = self._transformer(y)
if isinstance(i, slice):
self._list[i] = (self._transformer(v) for v in y)
else:
self._list[i] = self._transformer(y)

def __setslice__(self, i, j, y):
self._list[i:j] = (self._transformer(v) for v in y)
Expand Down Expand Up @@ -72,7 +83,7 @@ def find_files(name_or_names, paths, matches=None):
yielded value is the full path to a matching file.
"""

if isinstance(name_or_names, basestring):
if isinstance(name_or_names, string_type):
name_or_names = [name_or_names]

if matches is None:
Expand Down Expand Up @@ -138,9 +149,9 @@ def convention_loader(format_string):
not contain one and only one '%s' within it.
"""
if not format_string:
raise TypeError, 'format_string cannot be blank'
raise TypeError('format_string cannot be blank')
if re.findall('%.', format_string) != ['%s']:
raise TypeError, 'format_string must contain one and only one "%s" token'
raise TypeError('format_string must contain one and only one "%s" token')

def func(name):
return get_by_fqn(format_string % name)
Expand Down
Loading