* Testtools now depends on extras, a small library split out from it to contain
generally useful non-testing facilities. Since extras has been around for a couple of testtools releases now, we're making this into a hard dependency of testtools. (Robert Collins) * Testtools now uses setuptools rather than distutils so that we can document the extras dependency. (Robert Collins)
This commit is contained in:
@@ -8,3 +8,4 @@ apidocs
|
||||
_trial_temp
|
||||
doc/_build
|
||||
./.testrepository
|
||||
./testtools.egg-info
|
||||
|
||||
11
NEWS
11
NEWS
@@ -6,6 +6,17 @@ Changes and improvements to testtools_, grouped by release.
|
||||
NEXT
|
||||
~~~~
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
* Testtools now depends on extras, a small library split out from it to contain
|
||||
generally useful non-testing facilities. Since extras has been around for a
|
||||
couple of testtools releases now, we're making this into a hard dependency of
|
||||
testtools. (Robert Collins)
|
||||
|
||||
* Testtools now uses setuptools rather than distutils so that we can document
|
||||
the extras dependency. (Robert Collins)
|
||||
|
||||
0.9.24
|
||||
~~~~~~
|
||||
|
||||
|
||||
3
README
3
README
@@ -36,6 +36,9 @@ Required Dependencies
|
||||
If you would like to use testtools for earlier Python's, please use testtools
|
||||
0.9.15.
|
||||
|
||||
* extras (helpers that we intend to push into Python itself in the near
|
||||
future).
|
||||
|
||||
|
||||
Optional Dependencies
|
||||
---------------------
|
||||
|
||||
@@ -1288,7 +1288,7 @@ Conditional imports
|
||||
-------------------
|
||||
|
||||
Lots of the time we would like to conditionally import modules. testtools
|
||||
needs to do this itself, and graciously extends the ability to its users.
|
||||
uses the small library extras to do this. This used to be part of testtools.
|
||||
|
||||
Instead of::
|
||||
|
||||
@@ -1317,9 +1317,9 @@ You can do::
|
||||
Safe attribute testing
|
||||
----------------------
|
||||
|
||||
``hasattr`` is broken_ on many versions of Python. testtools provides
|
||||
``safe_hasattr``, which can be used to safely test whether an object has a
|
||||
particular attribute.
|
||||
``hasattr`` is broken_ on many versions of Python. The helper ``safe_hasattr``
|
||||
can be used to safely test whether an object has a particular attribute. Like
|
||||
``try_import`` this used to be in testtools but is now in extras.
|
||||
|
||||
|
||||
Nullary callables
|
||||
|
||||
8
setup.py
8
setup.py
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
"""Distutils installer for testtools."""
|
||||
|
||||
from distutils.core import setup
|
||||
from setuptools import setup
|
||||
import email
|
||||
import os
|
||||
|
||||
@@ -82,4 +82,8 @@ setup(name='testtools',
|
||||
'testtools.tests.matchers',
|
||||
],
|
||||
cmdclass={'test': testtools.TestCommand},
|
||||
zip_safe=False)
|
||||
zip_safe=False,
|
||||
install_requires=[
|
||||
'extras',
|
||||
],
|
||||
)
|
||||
|
||||
@@ -30,10 +30,12 @@ __all__ = [
|
||||
'try_imports',
|
||||
]
|
||||
|
||||
from testtools.helpers import (
|
||||
# Compat - removal announced in 0.9.25.
|
||||
from extras import (
|
||||
try_import,
|
||||
try_imports,
|
||||
)
|
||||
|
||||
from testtools.matchers._impl import (
|
||||
Matcher,
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ import sys
|
||||
import traceback
|
||||
import unicodedata
|
||||
|
||||
from testtools.helpers import try_imports
|
||||
from extras import try_imports
|
||||
|
||||
BytesIO = try_imports(['StringIO.StringIO', 'io.BytesIO'])
|
||||
StringIO = try_imports(['StringIO.StringIO', 'io.StringIO'])
|
||||
|
||||
@@ -17,7 +17,8 @@ import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from testtools import try_import
|
||||
from extras import try_import
|
||||
|
||||
from testtools.compat import _b, _format_exc_info, str_is_unicode, _u
|
||||
from testtools.content_type import ContentType, JSON, UTF8_TEXT
|
||||
|
||||
|
||||
@@ -8,83 +8,12 @@ __all__ = [
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def try_import(name, alternative=None, error_callback=None):
|
||||
"""Attempt to import ``name``. If it fails, return ``alternative``.
|
||||
|
||||
When supporting multiple versions of Python or optional dependencies, it
|
||||
is useful to be able to try to import a module.
|
||||
|
||||
:param name: The name of the object to import, e.g. ``os.path`` or
|
||||
``os.path.join``.
|
||||
:param alternative: The value to return if no module can be imported.
|
||||
Defaults to None.
|
||||
:param error_callback: If non-None, a callable that is passed the ImportError
|
||||
when the module cannot be loaded.
|
||||
"""
|
||||
module_segments = name.split('.')
|
||||
last_error = None
|
||||
while module_segments:
|
||||
module_name = '.'.join(module_segments)
|
||||
try:
|
||||
module = __import__(module_name)
|
||||
except ImportError:
|
||||
last_error = sys.exc_info()[1]
|
||||
module_segments.pop()
|
||||
continue
|
||||
else:
|
||||
break
|
||||
else:
|
||||
if last_error is not None and error_callback is not None:
|
||||
error_callback(last_error)
|
||||
return alternative
|
||||
nonexistent = object()
|
||||
for segment in name.split('.')[1:]:
|
||||
module = getattr(module, segment, nonexistent)
|
||||
if module is nonexistent:
|
||||
if last_error is not None and error_callback is not None:
|
||||
error_callback(last_error)
|
||||
return alternative
|
||||
return module
|
||||
|
||||
|
||||
_RAISE_EXCEPTION = object()
|
||||
def try_imports(module_names, alternative=_RAISE_EXCEPTION, error_callback=None):
|
||||
"""Attempt to import modules.
|
||||
|
||||
Tries to import the first module in ``module_names``. If it can be
|
||||
imported, we return it. If not, we go on to the second module and try
|
||||
that. The process continues until we run out of modules to try. If none
|
||||
of the modules can be imported, either raise an exception or return the
|
||||
provided ``alternative`` value.
|
||||
|
||||
:param module_names: A sequence of module names to try to import.
|
||||
:param alternative: The value to return if no module can be imported.
|
||||
If unspecified, we raise an ImportError.
|
||||
:param error_callback: If None, called with the ImportError for *each*
|
||||
module that fails to load.
|
||||
:raises ImportError: If none of the modules can be imported and no
|
||||
alternative value was specified.
|
||||
"""
|
||||
module_names = list(module_names)
|
||||
for module_name in module_names:
|
||||
module = try_import(module_name, error_callback=error_callback)
|
||||
if module:
|
||||
return module
|
||||
if alternative is _RAISE_EXCEPTION:
|
||||
raise ImportError(
|
||||
"Could not import any of: %s" % ', '.join(module_names))
|
||||
return alternative
|
||||
|
||||
|
||||
def safe_hasattr(obj, attr, _marker=object()):
|
||||
"""Does 'obj' have an attribute 'attr'?
|
||||
|
||||
Use this rather than built-in hasattr, as the built-in swallows exceptions
|
||||
in some versions of Python and behaves unpredictably with respect to
|
||||
properties.
|
||||
"""
|
||||
return getattr(obj, attr, _marker) is not _marker
|
||||
# Compat - removal announced in 0.9.25.
|
||||
from extras import (
|
||||
safe_hasattr,
|
||||
try_import,
|
||||
try_imports,
|
||||
)
|
||||
|
||||
|
||||
def map_values(function, dictionary):
|
||||
|
||||
@@ -20,9 +20,10 @@ import sys
|
||||
import types
|
||||
import unittest
|
||||
|
||||
from extras import try_import
|
||||
|
||||
from testtools import (
|
||||
content,
|
||||
try_import,
|
||||
)
|
||||
from testtools.compat import (
|
||||
advance_iterator,
|
||||
|
||||
@@ -16,12 +16,13 @@ import datetime
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from extras import safe_hasattr
|
||||
|
||||
from testtools.compat import all, str_is_unicode, _u
|
||||
from testtools.content import (
|
||||
text_content,
|
||||
TracebackContent,
|
||||
)
|
||||
from testtools.helpers import safe_hasattr
|
||||
from testtools.tags import TagContext
|
||||
|
||||
# From http://docs.python.org/library/datetime.html
|
||||
|
||||
@@ -8,10 +8,9 @@ __all__ = [
|
||||
|
||||
import sys
|
||||
|
||||
from extras import safe_hasattr
|
||||
|
||||
from testtools import TestResult
|
||||
from testtools.helpers import (
|
||||
safe_hasattr,
|
||||
)
|
||||
from testtools.content import TracebackContent
|
||||
from testtools import runtest
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
import os
|
||||
import signal
|
||||
|
||||
from extras import try_import
|
||||
|
||||
from testtools import (
|
||||
skipIf,
|
||||
TestCase,
|
||||
@@ -13,7 +15,6 @@ from testtools import (
|
||||
from testtools.content import (
|
||||
text_content,
|
||||
)
|
||||
from testtools.helpers import try_import
|
||||
from testtools.matchers import (
|
||||
Equals,
|
||||
KeysEqual,
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
|
||||
from distutils.dist import Distribution
|
||||
|
||||
from extras import try_import
|
||||
|
||||
from testtools.compat import (
|
||||
_b,
|
||||
_u,
|
||||
BytesIO,
|
||||
)
|
||||
from testtools.helpers import try_import
|
||||
fixtures = try_import('fixtures')
|
||||
|
||||
import testtools
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
import unittest
|
||||
|
||||
from extras import try_import
|
||||
|
||||
from testtools import (
|
||||
TestCase,
|
||||
content,
|
||||
content_type,
|
||||
)
|
||||
from testtools.compat import _b, _u
|
||||
from testtools.helpers import try_import
|
||||
from testtools.testresult.doubles import (
|
||||
ExtendedTestResult,
|
||||
)
|
||||
|
||||
@@ -1,196 +1,13 @@
|
||||
# Copyright (c) 2010-2012 testtools developers. See LICENSE for details.
|
||||
|
||||
from testtools import TestCase
|
||||
from testtools.helpers import (
|
||||
try_import,
|
||||
try_imports,
|
||||
)
|
||||
from testtools.matchers import (
|
||||
Equals,
|
||||
Is,
|
||||
Not,
|
||||
)
|
||||
from testtools.tests.helpers import (
|
||||
FullStackRunTest,
|
||||
hide_testtools_stack,
|
||||
is_stack_hidden,
|
||||
safe_hasattr,
|
||||
)
|
||||
|
||||
|
||||
def check_error_callback(test, function, arg, expected_error_count,
|
||||
expect_result):
|
||||
"""General test template for error_callback argument.
|
||||
|
||||
:param test: Test case instance.
|
||||
:param function: Either try_import or try_imports.
|
||||
:param arg: Name or names to import.
|
||||
:param expected_error_count: Expected number of calls to the callback.
|
||||
:param expect_result: Boolean for whether a module should
|
||||
ultimately be returned or not.
|
||||
"""
|
||||
cb_calls = []
|
||||
def cb(e):
|
||||
test.assertIsInstance(e, ImportError)
|
||||
cb_calls.append(e)
|
||||
try:
|
||||
result = function(arg, error_callback=cb)
|
||||
except ImportError:
|
||||
test.assertFalse(expect_result)
|
||||
else:
|
||||
if expect_result:
|
||||
test.assertThat(result, Not(Is(None)))
|
||||
else:
|
||||
test.assertThat(result, Is(None))
|
||||
test.assertEquals(len(cb_calls), expected_error_count)
|
||||
|
||||
|
||||
class TestSafeHasattr(TestCase):
|
||||
|
||||
def test_attribute_not_there(self):
|
||||
class Foo(object):
|
||||
pass
|
||||
self.assertEqual(False, safe_hasattr(Foo(), 'anything'))
|
||||
|
||||
def test_attribute_there(self):
|
||||
class Foo(object):
|
||||
pass
|
||||
foo = Foo()
|
||||
foo.attribute = None
|
||||
self.assertEqual(True, safe_hasattr(foo, 'attribute'))
|
||||
|
||||
def test_property_there(self):
|
||||
class Foo(object):
|
||||
@property
|
||||
def attribute(self):
|
||||
return None
|
||||
foo = Foo()
|
||||
self.assertEqual(True, safe_hasattr(foo, 'attribute'))
|
||||
|
||||
def test_property_raises(self):
|
||||
class Foo(object):
|
||||
@property
|
||||
def attribute(self):
|
||||
1/0
|
||||
foo = Foo()
|
||||
self.assertRaises(ZeroDivisionError, safe_hasattr, foo, 'attribute')
|
||||
|
||||
|
||||
class TestTryImport(TestCase):
|
||||
|
||||
def test_doesnt_exist(self):
|
||||
# try_import('thing', foo) returns foo if 'thing' doesn't exist.
|
||||
marker = object()
|
||||
result = try_import('doesntexist', marker)
|
||||
self.assertThat(result, Is(marker))
|
||||
|
||||
def test_None_is_default_alternative(self):
|
||||
# try_import('thing') returns None if 'thing' doesn't exist.
|
||||
result = try_import('doesntexist')
|
||||
self.assertThat(result, Is(None))
|
||||
|
||||
def test_existing_module(self):
|
||||
# try_import('thing', foo) imports 'thing' and returns it if it's a
|
||||
# module that exists.
|
||||
result = try_import('os', object())
|
||||
import os
|
||||
self.assertThat(result, Is(os))
|
||||
|
||||
def test_existing_submodule(self):
|
||||
# try_import('thing.another', foo) imports 'thing' and returns it if
|
||||
# it's a module that exists.
|
||||
result = try_import('os.path', object())
|
||||
import os
|
||||
self.assertThat(result, Is(os.path))
|
||||
|
||||
def test_nonexistent_submodule(self):
|
||||
# try_import('thing.another', foo) imports 'thing' and returns foo if
|
||||
# 'another' doesn't exist.
|
||||
marker = object()
|
||||
result = try_import('os.doesntexist', marker)
|
||||
self.assertThat(result, Is(marker))
|
||||
|
||||
def test_object_from_module(self):
|
||||
# try_import('thing.object') imports 'thing' and returns
|
||||
# 'thing.object' if 'thing' is a module and 'object' is not.
|
||||
result = try_import('os.path.join')
|
||||
import os
|
||||
self.assertThat(result, Is(os.path.join))
|
||||
|
||||
def test_error_callback(self):
|
||||
# the error callback is called on failures.
|
||||
check_error_callback(self, try_import, 'doesntexist', 1, False)
|
||||
|
||||
def test_error_callback_missing_module_member(self):
|
||||
# the error callback is called on failures to find an object
|
||||
# inside an existing module.
|
||||
check_error_callback(self, try_import, 'os.nonexistent', 1, False)
|
||||
|
||||
def test_error_callback_not_on_success(self):
|
||||
# the error callback is not called on success.
|
||||
check_error_callback(self, try_import, 'os.path', 0, True)
|
||||
|
||||
|
||||
class TestTryImports(TestCase):
|
||||
|
||||
def test_doesnt_exist(self):
|
||||
# try_imports('thing', foo) returns foo if 'thing' doesn't exist.
|
||||
marker = object()
|
||||
result = try_imports(['doesntexist'], marker)
|
||||
self.assertThat(result, Is(marker))
|
||||
|
||||
def test_fallback(self):
|
||||
result = try_imports(['doesntexist', 'os'])
|
||||
import os
|
||||
self.assertThat(result, Is(os))
|
||||
|
||||
def test_None_is_default_alternative(self):
|
||||
# try_imports('thing') returns None if 'thing' doesn't exist.
|
||||
e = self.assertRaises(
|
||||
ImportError, try_imports, ['doesntexist', 'noreally'])
|
||||
self.assertThat(
|
||||
str(e),
|
||||
Equals("Could not import any of: doesntexist, noreally"))
|
||||
|
||||
def test_existing_module(self):
|
||||
# try_imports('thing', foo) imports 'thing' and returns it if it's a
|
||||
# module that exists.
|
||||
result = try_imports(['os'], object())
|
||||
import os
|
||||
self.assertThat(result, Is(os))
|
||||
|
||||
def test_existing_submodule(self):
|
||||
# try_imports('thing.another', foo) imports 'thing' and returns it if
|
||||
# it's a module that exists.
|
||||
result = try_imports(['os.path'], object())
|
||||
import os
|
||||
self.assertThat(result, Is(os.path))
|
||||
|
||||
def test_nonexistent_submodule(self):
|
||||
# try_imports('thing.another', foo) imports 'thing' and returns foo if
|
||||
# 'another' doesn't exist.
|
||||
marker = object()
|
||||
result = try_imports(['os.doesntexist'], marker)
|
||||
self.assertThat(result, Is(marker))
|
||||
|
||||
def test_fallback_submodule(self):
|
||||
result = try_imports(['os.doesntexist', 'os.path'])
|
||||
import os
|
||||
self.assertThat(result, Is(os.path))
|
||||
|
||||
def test_error_callback(self):
|
||||
# One error for every class that doesn't exist.
|
||||
check_error_callback(self, try_imports,
|
||||
['os.doesntexist', 'os.notthiseither'],
|
||||
2, False)
|
||||
check_error_callback(self, try_imports,
|
||||
['os.doesntexist', 'os.notthiseither', 'os'],
|
||||
2, True)
|
||||
check_error_callback(self, try_imports,
|
||||
['os.path'],
|
||||
0, True)
|
||||
|
||||
|
||||
class TestStackHiding(TestCase):
|
||||
|
||||
run_tests_with = FullStackRunTest
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
|
||||
from unittest import TestSuite
|
||||
|
||||
from extras import try_import
|
||||
|
||||
from testtools.compat import (
|
||||
_b,
|
||||
StringIO,
|
||||
)
|
||||
from testtools.helpers import try_import
|
||||
fixtures = try_import('fixtures')
|
||||
|
||||
import testtools
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
import os
|
||||
import signal
|
||||
|
||||
from extras import try_import
|
||||
|
||||
from testtools import (
|
||||
skipIf,
|
||||
TestCase,
|
||||
)
|
||||
from testtools.helpers import try_import
|
||||
from testtools.matchers import (
|
||||
Equals,
|
||||
Is,
|
||||
|
||||
@@ -15,6 +15,8 @@ import threading
|
||||
from unittest import TestSuite
|
||||
import warnings
|
||||
|
||||
from extras import safe_hasattr
|
||||
|
||||
from testtools import (
|
||||
ExtendedToOriginalDecorator,
|
||||
MultiTestResult,
|
||||
@@ -44,7 +46,6 @@ from testtools.content import (
|
||||
TracebackContent,
|
||||
)
|
||||
from testtools.content_type import ContentType, UTF8_TEXT
|
||||
from testtools.helpers import safe_hasattr
|
||||
from testtools.matchers import (
|
||||
Contains,
|
||||
DocTestMatches,
|
||||
|
||||
@@ -6,13 +6,14 @@ __metaclass__ = type
|
||||
|
||||
import unittest
|
||||
|
||||
from extras import try_import
|
||||
|
||||
from testtools import (
|
||||
ConcurrentTestSuite,
|
||||
iterate_tests,
|
||||
PlaceHolder,
|
||||
TestCase,
|
||||
)
|
||||
from testtools.helpers import try_import
|
||||
from testtools.testsuite import FixtureSuite, iterate_tests, sorted_tests
|
||||
from testtools.tests.helpers import LoggingResult
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ __all__ = [
|
||||
'sorted_tests',
|
||||
]
|
||||
|
||||
from testtools.helpers import safe_hasattr, try_imports
|
||||
|
||||
Queue = try_imports(['Queue.Queue', 'queue.Queue'])
|
||||
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
from extras import safe_hasattr, try_imports
|
||||
|
||||
Queue = try_imports(['Queue.Queue', 'queue.Queue'])
|
||||
|
||||
import testtools
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user