_trial_temp
doc/_build
.testrepository
+.lp_creds
./testtools.egg-info
*.pyc
*.swp
python:
- "2.6"
- "2.7"
- - "3.2"
- "3.3"
- "pypy"
+# We have to pin Jinja2 < 2.7 for Python 3.2 because 2.7 drops/breaks support:
+# http://jinja.pocoo.org/docs/changelog/#version-2-7
+#
+# See also:
+# http://stackoverflow.com/questions/18252804/syntax-error-in-jinja-2-library
+matrix:
+ include:
+ - python: "3.2"
+ env: JINJA_REQ="jinja2<2.7"
+
install:
- - pip install -q --use-mirrors fixtures extras python-mimeparse
+ - pip install -q --use-mirrors fixtures extras python-mimeparse $JINJA_REQ sphinx
- python setup.py -q install
script:
- python -m testtools.run testtools.tests.test_suite
+ - make clean-sphinx docs
Changes
-------
-* Removed a number of code paths where Python 2.4 and Python 2.5 were
- explicitly handled. (Daniel Watkins)
+* Make testtools compatible with the ``unittest.expectedFailure`` decorator in
+ Python 3.4. (Thomi Richards)
Improvements
------------
* Introduce the assert_that function, which allows matchers to be used
independent of testtools.TestCase. (Daniel Watkins, #1243834)
+
+0.9.35
+~~~~~~
+
+Changes
+-------
+
+* Removed a number of code paths where Python 2.4 and Python 2.5 were
+ explicitly handled. (Daniel Watkins)
+
+Improvements
+------------
+
+* Added the ``testtools.TestCase.expectThat`` method, which implements
+ delayed assertions. (Thomi Richards)
+
+* Docs are now built as part of the Travis-CI build, reducing the chance of
+ Read The Docs being broken accidentally. (Daniel Watkins, #1158773)
+
0.9.34
~~~~~~
assert_that(result, Not(Equals(50)))
+=======
+Delayed Assertions
+~~~~~~~~~~~~~~~~~~
+
+A failure in the ``self.assertThat`` method will immediately fail the test: No
+more test code will be run after the assertion failure.
+
+The ``expectThat`` method behaves the same as ``assertThat`` with one
+exception: when failing the test it does so at the end of the test code rather
+than when the mismatch is detected. For example::
+
+ import subprocess
+
+ from testtools import TestCase
+ from testtools.matchers import Equals
+
+
+ class SomeProcessTests(TestCase):
+
+ def test_process_output(self):
+ process = subprocess.Popen(
+ ["my-app", "/some/path"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE
+ )
+
+ stdout, stderrr = process.communicate()
+
+ self.expectThat(process.returncode, Equals(0))
+ self.expectThat(stdout, Equals("Expected Output"))
+ self.expectThat(stderr, Equals(""))
+
+In this example, should the ``expectThat`` call fail, the failure will be
+recorded in the test result, but the test will continue as normal. If all
+three assertions fail, the test result will have three failures recorded, and
+the failure details for each failed assertion will be attached to the test
+result.
+
Stock matchers
--------------
for line in news:
line = line.strip()
if state is None:
- if is_heading_marker(line, '~'):
+ if (is_heading_marker(line, '~') and
+ not last_line.startswith('NEXT')):
milestone_name = last_line
state = 'release-notes'
else:
def main(args):
- launchpad = Launchpad.login_with(APP_NAME, SERVICE_ROOT, CACHE_DIR)
+ launchpad = Launchpad.login_with(
+ APP_NAME, SERVICE_ROOT, CACHE_DIR, credentials_file='.lp_creds')
return release_project(launchpad, PROJECT_NAME, NEXT_MILESTONE_NAME)
# If the releaselevel is 'final', then the tarball will be major.minor.micro.
# Otherwise it is major.minor.micro~$(revno).
-__version__ = (0, 9, 35, 'dev', 0)
+__version__ = (0, 9, 36, 'dev', 0)
_b,
_format_exception_only,
_format_stack_list,
+ _isbytes,
_TB_HEADER,
_u,
str_is_unicode,
This is useful for adding details which are short strings.
"""
+ if _isbytes(text):
+ raise TypeError('text_content must be given a string, not bytes.')
return Content(UTF8_TEXT, lambda: [text.encode('utf8')])
]
import copy
+import functools
import itertools
import sys
import types
from testtools import (
content,
)
-from testtools.assertions import (
- assert_that,
-)
from testtools.compat import (
advance_iterator,
reraise,
'unittest.case._ExpectedFailure', _ExpectedFailure)
+# Copied from unittest before python 3.4 release. Used to maintain
+# compatibility with unittest sub-test feature. Users should not use this
+# directly.
+def _expectedFailure(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ try:
+ func(*args, **kwargs)
+ except Exception:
+ raise _ExpectedFailure(sys.exc_info())
+ raise _UnexpectedSuccess
+ return wrapper
+
+
def run_test_with(test_runner, **kwargs):
"""Decorate a test as using a specific ``RunTest``.
runTest = getattr(
test_method, '_run_test_with', self.run_tests_with)
self.__RunTest = runTest
+ if getattr(test_method, '__unittest_expecting_failure__', False):
+ setattr(self, self._testMethodName, _expectedFailure(test_method))
self.__exception_handlers = []
self.exception_handlers = [
(self.skipException, self._report_skip),
failUnlessEqual = assertEquals = assertEqual
- def assertIn(self, needle, haystack):
+ def assertIn(self, needle, haystack, message=''):
"""Assert that needle is in haystack."""
- self.assertThat(haystack, Contains(needle))
+ self.assertThat(haystack, Contains(needle), message)
def assertIsNone(self, observed, message=''):
"""Assert that 'observed' is equal to None.
matcher = Not(Is(expected))
self.assertThat(observed, matcher, message)
- def assertNotIn(self, needle, haystack):
+ def assertNotIn(self, needle, haystack, message=''):
"""Assert that needle is not in haystack."""
matcher = Not(Contains(needle))
- self.assertThat(haystack, matcher)
+ self.assertThat(haystack, matcher, message)
def assertIsInstance(self, obj, klass, msg=None):
if isinstance(klass, tuple):
:param matcher: An object meeting the testtools.Matcher protocol.
:raises MismatchError: When matcher does not match thing.
"""
- try:
- assert_that(matchee, matcher, message, verbose)
- except MismatchError as error:
- mismatch = error.mismatch
- for (name, content) in mismatch.get_details().items():
- self.addDetailUniqueName(name, content)
- raise
+ mismatch_error = self._matchHelper(matchee, matcher, message, verbose)
+ if mismatch_error is not None:
+ raise mismatch_error
def addDetailUniqueName(self, name, content_object):
"""Add a detail to the test, but ensure it's name is unique.
suffix += 1
self.addDetail(full_name, content_object)
+ def expectThat(self, matchee, matcher, message='', verbose=False):
+ """Check that matchee is matched by matcher, but delay the assertion failure.
+
+ This method behaves similarly to ``assertThat``, except that a failed
+ match does not exit the test immediately. The rest of the test code will
+ continue to run, and the test will be marked as failing after the test
+ has finished.
+
+ :param matchee: An object to match with matcher.
+ :param matcher: An object meeting the testtools.Matcher protocol.
+ :param message: If specified, show this message with any failed match.
+ """
+ mismatch_error = self._matchHelper(matchee, matcher, message, verbose)
+
+ if mismatch_error is not None:
+ self.addDetailUniqueName(
+ "Failed expectation",
+ content.StacktraceContent(
+ postfix_content="MismatchError: " + str(mismatch_error)
+ )
+ )
+ self.force_failure = True
+
+ def _matchHelper(self, matchee, matcher, message, verbose):
+ matcher = Annotate.if_message(message, matcher)
+ mismatch = matcher.match(matchee)
+ if not mismatch:
+ return
+ for (name, value) in mismatch.get_details().items():
+ self.addDetailUniqueName(name, value)
+ return MismatchError(matchee, matcher, mismatch, verbose)
+
def defaultTestResult(self):
return TestResult()
import tempfile
import unittest
-from testtools import TestCase
+from testtools import TestCase, skipUnless
from testtools.compat import (
_b,
_u,
BytesIO,
StringIO,
+ str_is_unicode,
)
from testtools.content import (
attach_file,
expected = Content(UTF8_TEXT, lambda: [data.encode('utf8')])
self.assertEqual(expected, text_content(data))
+ @skipUnless(str_is_unicode, "Test only applies in python 3.")
+ def test_text_content_raises_TypeError_when_passed_bytes(self):
+ data = _b("Some Bytes")
+ self.assertRaises(TypeError, text_content, data)
+
def test_json_content(self):
data = {'foo': 'bar'}
expected = Content(JSON, lambda: [_b('{"foo": "bar"}')])
)
from testtools.content import (
text_content,
+ TracebackContent,
)
from testtools.matchers import (
Annotate,
'%r not in %r' % ('qux', 'foo bar baz'),
self.assertIn, 'qux', 'foo bar baz')
+ def test_assertIn_failure_with_message(self):
+ # assertIn(needle, haystack) fails the test when 'needle' is not in
+ # 'haystack'.
+ self.assertFails('3 not in [0, 1, 2]: foo bar', self.assertIn, 3,
+ [0, 1, 2], 'foo bar')
+ self.assertFails(
+ '%r not in %r: foo bar' % ('qux', 'foo bar baz'),
+ self.assertIn, 'qux', 'foo bar baz', 'foo bar')
+
+
def test_assertNotIn_success(self):
# assertNotIn(needle, haystack) asserts that 'needle' is not in
# 'haystack'.
"'foo bar baz' matches Contains('foo')",
self.assertNotIn, 'foo', 'foo bar baz')
+
+ def test_assertNotIn_failure_with_message(self):
+ # assertNotIn(needle, haystack) fails the test when 'needle' is in
+ # 'haystack'.
+ self.assertFails('[1, 2, 3] matches Contains(3): foo bar', self.assertNotIn,
+ 3, [1, 2, 3], 'foo bar')
+ self.assertFails(
+ "'foo bar baz' matches Contains('foo'): foo bar",
+ self.assertNotIn, 'foo', 'foo bar baz', "foo bar")
+
+
+
def test_assertIsInstance(self):
# assertIsInstance asserts that an object is an instance of a class.
'None matches Is(None): foo bar', self.assertIsNot, None, None,
"foo bar")
+ def test_assertThat_matches_clean(self):
+ class Matcher(object):
+ def match(self, foo):
+ return None
+ self.assertThat("foo", Matcher())
+
+ def test_assertThat_mismatch_raises_description(self):
+ calls = []
+ class Mismatch(object):
+ def __init__(self, thing):
+ self.thing = thing
+ def describe(self):
+ calls.append(('describe_diff', self.thing))
+ return "object is not a thing"
+ def get_details(self):
+ return {}
+ class Matcher(object):
+ def match(self, thing):
+ calls.append(('match', thing))
+ return Mismatch(thing)
+ def __str__(self):
+ calls.append(('__str__',))
+ return "a description"
+ class Test(TestCase):
+ def test(self):
+ self.assertThat("foo", Matcher())
+ result = Test("test").run()
+ self.assertEqual([
+ ('match', "foo"),
+ ('describe_diff', "foo"),
+ ], calls)
+ self.assertFalse(result.wasSuccessful())
+
+ def test_assertThat_output(self):
+ matchee = 'foo'
+ matcher = Equals('bar')
+ expected = matcher.match(matchee).describe()
+ self.assertFails(expected, self.assertThat, matchee, matcher)
+
+ def test_assertThat_message_is_annotated(self):
+ matchee = 'foo'
+ matcher = Equals('bar')
+ expected = Annotate('woo', matcher).match(matchee).describe()
+ self.assertFails(expected, self.assertThat, matchee, matcher, 'woo')
+
+ def test_assertThat_verbose_output(self):
+ matchee = 'foo'
+ matcher = Equals('bar')
+ expected = (
+ 'Match failed. Matchee: %r\n'
+ 'Matcher: %s\n'
+ 'Difference: %s\n' % (
+ matchee,
+ matcher,
+ matcher.match(matchee).describe(),
+ ))
+ self.assertFails(
+ expected, self.assertThat, matchee, matcher, verbose=True)
+
+ def test_expectThat_matches_clean(self):
+ class Matcher(object):
+ def match(self, foo):
+ return None
+ self.expectThat("foo", Matcher())
+
+ def test_expectThat_mismatch_fails_test(self):
+ class Test(TestCase):
+ def test(self):
+ self.expectThat("foo", Equals("bar"))
+ result = Test("test").run()
+ self.assertFalse(result.wasSuccessful())
+
+ def test_expectThat_does_not_exit_test(self):
+ class Test(TestCase):
+ marker = False
+ def test(self):
+ self.expectThat("foo", Equals("bar"))
+ Test.marker = True
+ result = Test("test").run()
+ self.assertFalse(result.wasSuccessful())
+ self.assertTrue(Test.marker)
+
+ def test_expectThat_adds_detail(self):
+ class Test(TestCase):
+ def test(self):
+ self.expectThat("foo", Equals("bar"))
+ test = Test("test")
+ result = test.run()
+ details = test.getDetails()
+ self.assertTrue("Failed expectation" in details)
+
def test__force_failure_fails_test(self):
class Test(TestCase):
def test_foo(self):
self.assertFalse(result.wasSuccessful())
self.assertTrue(test.remaining_code_run)
+ def get_error_string(self, e):
+ """Get the string showing how 'e' would be formatted in test output.
+
+ This is a little bit hacky, since it's designed to give consistent
+ output regardless of Python version.
+
+ In testtools, TestResult._exc_info_to_unicode is the point of dispatch
+ between various different implementations of methods that format
+ exceptions, so that's what we have to call. However, that method cares
+ about stack traces and formats the exception class. We don't care
+ about either of these, so we take its output and parse it a little.
+ """
+ error = TracebackContent((e.__class__, e, None), self).as_text()
+ # We aren't at all interested in the traceback.
+ if error.startswith('Traceback (most recent call last):\n'):
+ lines = error.splitlines(True)[1:]
+ for i, line in enumerate(lines):
+ if not line.startswith(' '):
+ break
+ error = ''.join(lines[i:])
+ # We aren't interested in how the exception type is formatted.
+ exc_class, error = error.split(': ', 1)
+ return error
+
+ def test_assertThat_verbose_unicode(self):
+ # When assertThat is given matchees or matchers that contain non-ASCII
+ # unicode strings, we can still provide a meaningful error.
+ matchee = _u('\xa7')
+ matcher = Equals(_u('a'))
+ expected = (
+ 'Match failed. Matchee: %s\n'
+ 'Matcher: %s\n'
+ 'Difference: %s\n\n' % (
+ repr(matchee).replace("\\xa7", matchee),
+ matcher,
+ matcher.match(matchee).describe(),
+ ))
+ e = self.assertRaises(
+ self.failureException, self.assertThat, matchee, matcher,
+ verbose=True)
+ self.assertEqual(expected, self.get_error_string(e))
+
def test_assertEqual_nice_formatting(self):
message = "These things ought not be equal."
a = ['apple', 'banana', 'cherry']
self.assertDetailsProvided(case, "addUnexpectedSuccess",
["foo", "reason"])
+ @skipIf(not hasattr(unittest, 'expectedFailure'), 'Need py27+')
+ def test_unittest_expectedFailure_decorator_works_with_failure(self):
+ class ReferenceTest(TestCase):
+ @unittest.expectedFailure
+ def test_fails_expectedly(self):
+ self.assertEquals(1, 0)
+
+ test = ReferenceTest('test_fails_expectedly')
+ result = test.run()
+ self.assertEqual(True, result.wasSuccessful())
+
+ @skipIf(not hasattr(unittest, 'expectedFailure'), 'Need py27+')
+ def test_unittest_expectedFailure_decorator_works_with_success(self):
+ class ReferenceTest(TestCase):
+ @unittest.expectedFailure
+ def test_passes_unexpectedly(self):
+ self.assertEquals(1, 1)
+
+ test = ReferenceTest('test_passes_unexpectedly')
+ result = test.run()
+ self.assertEqual(False, result.wasSuccessful())
+
class TestUniqueFactories(TestCase):
"""Tests for getUniqueString and getUniqueInteger."""