Index: Doc/library/unittest.rst =================================================================== --- Doc/library/unittest.rst (revision 85670) +++ Doc/library/unittest.rst (working copy) @@ -798,7 +798,8 @@ .. versionchanged:: 3.2 :meth:`assertMultiLineEqual` added as the default type equality - function for comparing strings. + function for comparing strings, and :meth:`assertBytesMultiLineEqual` + for comparing bytes. .. deprecated:: 3.1 :meth:`failUnlessEqual`; use :meth:`assertEqual`. @@ -895,6 +896,18 @@ .. versionadded:: 3.1 + .. method:: assertBytesMultiLineEqual(self, first, second, msg=None) + + Test that the multiline byte string *first* is equal to the string *second*. + When not equal a diff of the two strings highlighting the differences + will be included in the error message. This method is used by default + when comparing bytes objects with :meth:`assertEqual`. + + If specified, *msg* will be used as the error message on failure. + + .. versionadded:: 3.2 + + .. method:: assertRegexpMatches(text, regexp, msg=None) Verifies that a *regexp* search matches *text*. Fails with an error @@ -1234,8 +1247,8 @@ methods that report diffs on failure. It defaults to 80*8 characters. Assert methods affected by this attribute are :meth:`assertSequenceEqual` (including all the sequence comparison - methods that delegate to it), :meth:`assertDictEqual` and - :meth:`assertMultiLineEqual`. + methods that delegate to it), :meth:`assertDictEqual`, + :meth:`assertMultiLineEqual` and :meth:`assertBytesMultiLineEqual`. Setting ``maxDiff`` to None means that there is no maximum length of diffs. Index: Lib/unittest/test/test_case.py =================================================================== --- Lib/unittest/test/test_case.py (revision 85670) +++ Lib/unittest/test/test_case.py (working copy) @@ -671,6 +671,18 @@ else: self.fail('assertMultiLineEqual did not fail') + def testAssertBytesMultiLineEqualTruncates(self): + test = unittest.TestCase('assertEqual') + def truncate(msg, diff): + return 'foo' + test._truncateMessage = truncate + try: + test.assertBytesMultiLineEqual(b'foo', b'bar') + except self.failureException as e: + self.assertEqual(str(e), 'foo') + else: + self.fail('assertBytesMultiLineEqual did not fail') + def testAssertItemsEqual(self): a = object() self.assertItemsEqual([1, 2, 3], [3, 2, 1]) @@ -843,6 +855,40 @@ # so can't use assertEqual either. Just use assertTrue. self.assertTrue(sample_text_error == error) + def testAssertBytesMultiLineEqual(self): + sample_text = b"""\ +http://www.python.org/doc/2.3/lib/module-unittest.html +test case + A test case is the smallest unit of testing. [...] +""" + revised_sample_text = b"""\ +http://www.python.org/doc/2.4.1/lib/module-unittest.html +test case + A test case is the smallest unit of testing. [...] You may provide your + own implementation that does not subclass from TestCase, of course. +""" + sample_text_error = """\ +- b'http://www.python.org/doc/2.3/lib/module-unittest.html\\n' +? ^ ++ b'http://www.python.org/doc/2.4.1/lib/module-unittest.html\\n' +? ^^^ + b'test case\\n' +- b' A test case is the smallest unit of testing. [...]\\n' ++ b' A test case is the smallest unit of testing. [...] You may provide your\\n' +? +++++++++++++++++++++ ++ b' own implementation that does not subclass from TestCase, of course.\\n' +""" + self.maxDiff = None + try: + self.assertBytesMultiLineEqual(sample_text, revised_sample_text) + except self.failureException as e: + # need to remove the first line of the error message + error = str(e).split('\n', 1)[1] + # We're comparing strings here, so we can use assertEqual. But be + # warned, if this fails you have to look at the output carefully to + # sort out the real failures from the sample failure lines :) + self.assertEqual(sample_text_error, error) + def testAsertEqualSingleLine(self): sample_text = "laden swallows fly slowly" revised_sample_text = "unladen swallows fly quickly" Index: Lib/unittest/case.py =================================================================== --- Lib/unittest/case.py (revision 85670) +++ Lib/unittest/case.py (working copy) @@ -260,6 +260,7 @@ self.addTypeEqualityFunc(set, self.assertSetEqual) self.addTypeEqualityFunc(frozenset, self.assertSetEqual) self.addTypeEqualityFunc(str, self.assertMultiLineEqual) + self.addTypeEqualityFunc(bytes, self.assertBytesMultiLineEqual) def addTypeEqualityFunc(self, typeobj, function): """Add a type specific assertEqual style function to compare a type. @@ -1035,6 +1036,24 @@ standardMsg = self._truncateMessage(standardMsg, diff) self.fail(self._formatMessage(msg, standardMsg)) + def assertBytesMultiLineEqual(self, first, second, msg=None): + """Assert that two multi-line byte strings are equal.""" + self.assert_(isinstance(first, bytes), ( + 'First argument is not a byte string')) + self.assert_(isinstance(second, bytes), ( + 'Second argument is not a byte string')) + + if first != second: + firstlines = first.splitlines(True) + secondlines = second.splitlines(True) + standardMsg = '%s != %s' % (safe_repr(first, True), + safe_repr(second, True)) + firstlines = [repr(x)+'\n' for x in firstlines] + secondlines = [repr(x)+'\n' for x in secondlines] + diff = '\n' + ''.join(difflib.ndiff(firstlines, secondlines)) + standardMsg = self._truncateMessage(standardMsg, diff) + self.fail(self._formatMessage(msg, standardMsg)) + def assertLess(self, a, b, msg=None): """Just like self.assertTrue(a < b), but with a nicer default message.""" if not a < b: