testtools-dev team mailing list archive
-
testtools-dev team
-
Mailing list archive
-
Message #01155
[Merge] lp:~lifeless/testtools/useextras into lp:testtools
Robert Collins has proposed merging lp:~lifeless/testtools/useextras into lp:testtools.
Requested reviews:
testtools committers (testtools-committers)
For more details, see:
https://code.launchpad.net/~lifeless/testtools/useextras/+merge/143653
We split extras out. Now use it.
--
https://code.launchpad.net/~lifeless/testtools/useextras/+merge/143653
Your team testtools developers is subscribed to branch lp:testtools.
=== modified file 'NEWS'
--- NEWS 2012-12-19 12:10:59 +0000
+++ NEWS 2013-01-17 09:28:19 +0000
@@ -6,6 +6,15 @@
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. The ``safe_hasattr``, ``try_import`` and ``try_imports`` symbols
+ are no longer exported from testtools. (Robert Collins)
+
0.9.24
~~~~~~
=== modified file 'doc/for-test-authors.rst'
--- doc/for-test-authors.rst 2012-11-29 11:40:17 +0000
+++ doc/for-test-authors.rst 2013-01-17 09:28:19 +0000
@@ -1288,7 +1288,7 @@
-------------------
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 @@
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
=== modified file 'testtools/__init__.py'
--- testtools/__init__.py 2012-12-19 12:10:59 +0000
+++ testtools/__init__.py 2013-01-17 09:28:19 +0000
@@ -26,14 +26,8 @@
'skipIf',
'skipUnless',
'ThreadsafeForwardingResult',
- 'try_import',
- 'try_imports',
]
-from testtools.helpers import (
- try_import,
- try_imports,
- )
from testtools.matchers._impl import (
Matcher,
)
=== modified file 'testtools/compat.py'
--- testtools/compat.py 2012-10-20 17:44:19 +0000
+++ testtools/compat.py 2013-01-17 09:28:19 +0000
@@ -27,7 +27,7 @@
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'])
=== modified file 'testtools/content.py'
--- testtools/content.py 2012-12-10 22:57:03 +0000
+++ testtools/content.py 2013-01-17 09:28:19 +0000
@@ -17,7 +17,8 @@
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
=== modified file 'testtools/helpers.py'
--- testtools/helpers.py 2012-08-10 14:05:57 +0000
+++ testtools/helpers.py 2013-01-17 09:28:19 +0000
@@ -1,92 +1,11 @@
# Copyright (c) 2010-2012 testtools developers. See LICENSE for details.
__all__ = [
- 'safe_hasattr',
- 'try_import',
- 'try_imports',
]
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
-
-
def map_values(function, dictionary):
"""Map ``function`` across the values of ``dictionary``.
=== modified file 'testtools/testcase.py'
--- testtools/testcase.py 2012-04-03 07:39:43 +0000
+++ testtools/testcase.py 2013-01-17 09:28:19 +0000
@@ -20,9 +20,10 @@
import types
import unittest
+from extras import try_import
+
from testtools import (
content,
- try_import,
)
from testtools.compat import (
advance_iterator,
=== modified file 'testtools/testresult/real.py'
--- testtools/testresult/real.py 2012-12-18 09:43:43 +0000
+++ testtools/testresult/real.py 2013-01-17 09:28:19 +0000
@@ -16,12 +16,13 @@
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
=== modified file 'testtools/tests/helpers.py'
--- testtools/tests/helpers.py 2012-12-15 13:02:26 +0000
+++ testtools/tests/helpers.py 2013-01-17 09:28:19 +0000
@@ -8,10 +8,9 @@
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
=== modified file 'testtools/tests/test_deferredruntest.py'
--- testtools/tests/test_deferredruntest.py 2012-02-06 16:02:02 +0000
+++ testtools/tests/test_deferredruntest.py 2013-01-17 09:28:19 +0000
@@ -5,6 +5,8 @@
import os
import signal
+from extras import try_import
+
from testtools import (
skipIf,
TestCase,
@@ -13,7 +15,6 @@
from testtools.content import (
text_content,
)
-from testtools.helpers import try_import
from testtools.matchers import (
Equals,
KeysEqual,
=== modified file 'testtools/tests/test_distutilscmd.py'
--- testtools/tests/test_distutilscmd.py 2012-12-17 00:57:44 +0000
+++ testtools/tests/test_distutilscmd.py 2013-01-17 09:28:19 +0000
@@ -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
=== modified file 'testtools/tests/test_fixturesupport.py'
--- testtools/tests/test_fixturesupport.py 2012-02-06 16:02:02 +0000
+++ testtools/tests/test_fixturesupport.py 2013-01-17 09:28:19 +0000
@@ -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,
)
=== modified file 'testtools/tests/test_helpers.py'
--- testtools/tests/test_helpers.py 2012-02-04 16:44:10 +0000
+++ testtools/tests/test_helpers.py 2013-01-17 09:28:19 +0000
@@ -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
=== modified file 'testtools/tests/test_run.py'
--- testtools/tests/test_run.py 2012-12-18 09:03:19 +0000
+++ testtools/tests/test_run.py 2013-01-17 09:28:19 +0000
@@ -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
=== modified file 'testtools/tests/test_spinner.py'
--- testtools/tests/test_spinner.py 2011-01-22 17:56:00 +0000
+++ testtools/tests/test_spinner.py 2013-01-17 09:28:19 +0000
@@ -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,
=== modified file 'testtools/tests/test_testresult.py'
--- testtools/tests/test_testresult.py 2012-12-15 13:02:26 +0000
+++ testtools/tests/test_testresult.py 2013-01-17 09:28:19 +0000
@@ -15,6 +15,8 @@
from unittest import TestSuite
import warnings
+from extras import safe_hasattr
+
from testtools import (
ExtendedToOriginalDecorator,
MultiTestResult,
@@ -44,7 +46,6 @@
TracebackContent,
)
from testtools.content_type import ContentType, UTF8_TEXT
-from testtools.helpers import safe_hasattr
from testtools.matchers import (
Contains,
DocTestMatches,
=== modified file 'testtools/tests/test_testsuite.py'
--- testtools/tests/test_testsuite.py 2012-12-18 09:03:19 +0000
+++ testtools/tests/test_testsuite.py 2013-01-17 09:28:19 +0000
@@ -6,13 +6,14 @@
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
=== modified file 'testtools/testsuite.py'
--- testtools/testsuite.py 2012-12-18 10:19:38 +0000
+++ testtools/testsuite.py 2013-01-17 09:28:19 +0000
@@ -9,13 +9,13 @@
'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
Follow ups