Merge lp:~mbp/bzr/test-errors into lp:bzr

Proposed by Martin Pool
Status: Work in progress
Proposed branch: lp:~mbp/bzr/test-errors
Merge into: lp:bzr
Diff against target: 554 lines (+176/-116)
4 files modified
bzrlib/osutils.py (+10/-0)
bzrlib/tests/__init__.py (+121/-61)
bzrlib/tests/test_selftest.py (+36/-46)
bzrlib/tests/test_trace.py (+9/-9)
To merge this branch: bzr merge lp:~mbp/bzr/test-errors
Reviewer Review Type Date Requested Status
Martin Pool Approve
Martin Packman (community) Needs Information
John A Meinel Approve
Review via email: mp+64485@code.launchpad.net

Commit message

multiple selftest improvements: print errors once; no traceback for xfail; no empty logs (bug 499713, bug 625597)

Description of the change

A few selftest annoyances fixed:

* Errors are printed only once, as they occur, not again at the end.
  If someone actually really likes this, the code is there and you can
  add a configuration to turn it on. (See bug 408192, bug 625597, though
  this is not exactly what was discussed.)

* UnicodeOrBytesToBytesWriter had the somewhat confusing behavior of
  printing the repr of the wrapped object.

* The indentation of test failures did not fit well with testtools' behavior
  of returning the whole traceback as part of the error string.

* We shouldn't print the whole traceback for known failures. Just
  the reason string is enough. (Bug 499713)

* To let the unicode-escaping code work, we need to pass it the error
  as a unicode string.

* Don't attach empty log files; as a consequence the log is not attached
  until the test completes. (However, it can still be read from
  TestCase.get_log()).

* Bugs in old subunits do not deserve a 'known failure' in bzr. Only
  things we could fix (without time travel) should get that.

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 6/14/2011 5:51 AM, Martin Pool wrote:
> Martin Pool has proposed merging lp:~mbp/bzr/test-errors into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> For more details, see:
> https://code.launchpad.net/~mbp/bzr/test-errors/+merge/64485
>
> A few selftest annoyances fixed:
>
> * Errors are printed only once, as they occur, not again at the end.
> If someone actually really likes this, the code is there and you can
> add a configuration to turn it on. (See bug 408192, bug 625597, though
> this is not exactly what was discussed.)
>
> * UnicodeOrBytesToBytesWriter had the somewhat confusing behavior of
> printing the repr of the wrapped object.
>
> * The indentation of test failures did not fit well with testtools' behavior
> of returning the whole traceback as part of the error string.
>
> * We shouldn't print the whole traceback for known failures. Just
> the reason string is enough. (Bug 499713)

Yay!

>
> * To let the unicode-escaping code work, we need to pass it the error
> as a unicode string.
>
> * Don't attach empty log files; as a consequence the log is not attached
> until the test completes. (However, it can still be read from
> TestCase.get_log()).

This also seems nice.

>
> * Bugs in old subunits do not deserve a 'known failure' in bzr. Only
> things we could fix (without time travel) should get that.

"We know this fails under these conditions", that seems like known
failure to me. However, I guess it could just be skipped.

Anyway, the code all looks good to me.

 merge: approve

John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk33eXgACgkQJdeBCYSNAAMXzgCaAik/3rhrICt85tsgBYVf584Z
hGUAoNC7d66TJ+xGIQYtT2GU85REikDc
=hh8p
-----END PGP SIGNATURE-----

review: Approve
Revision history for this message
Martin Pool (mbp) wrote :

On 14 June 2011 08:08, John Arbash Meinel <email address hidden> wrote:
>> * Bugs in old subunits do not deserve a 'known failure' in bzr.  Only
>>   things we could fix (without time travel) should get that.
>
> "We know this fails under these conditions", that seems like known
> failure to me. However, I guess it could just be skipped.

The distinction is supposed to be (in my mind, and I think in the
docs) that xfail is things that we should eventually come back and fix
in bzr; they should generally have a bug number. Skip is just things
that don't make sense to ever be tested. There are probably some that
are misclassified.

If there was a bug in a dependency, it would be reasonable to mark
that xfail until it's fixed. Once it is fixed, we might as well just
skip the test on old versions of that library, unless we choose to
also add a workaround in bzr itself. It is no longer a bug we need to
fix.

Thanks

Revision history for this message
Martin Pool (mbp) wrote :

sent to pqm by email

Revision history for this message
Martin Packman (gz) wrote :

What was the failure from PQM for this?

I'm a little scared by some of these changes, though I think overall it's moving in the right direction.

+ self.repeat_failures_at_end = False

My understanding of the proposed flag was it would be either during *or* at the end, never both.

+class BaseTextTestResult(ExtendedTestResult):

Why a new class rather than just putting the method you want to share on ExtendedTestResult?

+ u'ERROR: %s\n' % self._test_description(test))

Using u"" ascii literals is well intentioned but isn't really useful. This is something of a common misconception. All it amounts to is asking for an error from this interpolation rather than elsewhere in the stack. Having the file object reject non-ascii bytestrings is a better choice.

     def _finishLogFile(self):
...
+ log_contents = self.get_log()

Moving this logic scares me. Robert did a lot of complicated coding to make sure the log was available from test initialisation rather than only at the end, which is partly why this code is still strange. If we really don't want the log until _finishLogFile is called, most of the code can be removed and replaced with a single addDetails call here.

+ if type(codec) is tuple:
+ # Python 2.4
+ encode = codec[0]

This code being copied out to a helper method can lose the compat stuff now.

+ # This isn't an expected failure in bzr, because there's nothing
+ # reasonable we can do about old buggy subunits.

This was an expected failure because we want to depend on a minimum subunit version to avoid failures, and remove the need for the hack in the test, see bug 531667.

review: Needs Information
Revision history for this message
Martin Pool (mbp) wrote :

test_trace.TestTrace.test_mutter_never_failsERROR 0ms
   lost connection during error report of test 'bzrlib.tests.test_trace.TestTrace.test_mutter_never_fails'

thanks for the review comments

Revision history for this message
Martin Pool (mbp) wrote :
Download full text (3.7 KiB)

> What was the failure from PQM for this?
>
> I'm a little scared by some of these changes, though I think overall it's
> moving in the right direction.
>
> + self.repeat_failures_at_end = False
>
> My understanding of the proposed flag was it would be either during *or* at
> the end, never both.

This was a bit of a compromise towards people wanting it at the end. I think I will just pull it out. <https://bugs.launchpad.net/bzr/+bug/625597>.

>
> +class BaseTextTestResult(ExtendedTestResult):
>
> Why a new class rather than just putting the method you want to share on
> ExtendedTestResult?

My idea was that it could be good to separate out "general extended test result" from "... formatted as text." If the parent class already has text-specific methods there may be no point (or perhaps even if it doesn't.) I'll look.

> + u'ERROR: %s\n' % self._test_description(test))
>
> Using u"" ascii literals is well intentioned but isn't really useful. This is
> something of a common misconception. All it amounts to is asking for an error
> from this interpolation rather than elsewhere in the stack. Having the file
> object reject non-ascii bytestrings is a better choice.

This was actually done in response to a test failure and I think it does actually make sense. We want to keep it as unicode as late as possible, and then the file can either reject it or (what we actually want) discard unrepresentable characters.

> def _finishLogFile(self):
> ...
> + log_contents = self.get_log()
>
> Moving this logic scares me. Robert did a lot of complicated coding to make
> sure the log was available from test initialisation rather than only at the
> end, which is partly why this code is still strange. If we really don't want
> the log until _finishLogFile is called, most of the code can be removed and
> replaced with a single addDetails call here.

What behaviour do we actually want from the log file while testing? And secondly, how many tests count on the existing behaviour?

It seems to me that having tests able to inspect the log of every message emitted since the test started is not really a good idea. I have written such tests myself but they are fairly imprecise: if we are trying to check that a particular operation writes some debug messages, we will normally want to check that happens during a particular call, not during the whole run to date. I believe that holding the whole log in memory has also caused excess memory usage (and maybe you, gz, fixed it?)

I'll have a look into whether we can simplify this more.

>
> + if type(codec) is tuple:
> + # Python 2.4
> + encode = codec[0]
>
> This code being copied out to a helper method can lose the compat stuff now.

Thanks.

>
> + # This isn't an expected failure in bzr, because there's nothing
> + # reasonable we can do about old buggy subunits.
>
> This was an expected failure because we want to depend on a minimum subunit
> version to avoid failures, and remove the need for the hack in the test, see
> bug 531667.

OK, I see. I think we should only have xfails for things that would be valid bugs against bzr...

Read more...

Revision history for this message
Martin Pool (mbp) wrote :

sent to pqm by email

Revision history for this message
Martin Pool (mbp) wrote :
Download full text (4.2 KiB)

This fails in pqm with some kind of unicode error in subunit integration:

bzr: ERROR: exceptions.UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 30: ordinal not in range(128)

Traceback (most recent call last):
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/commands.py", line 946, in exception_to_return_code
   return the_callable(*args, **kwargs)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/commands.py", line 1150, in run_bzr
   ret = run(*run_argv)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/commands.py", line 699, in run_argv_aliases
   return self.run(**all_cmd_args)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/commands.py", line 721, in run
   return self._operation.run_simple(*args, **kwargs)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/cleanup.py", line 135, in run_simple
   self.cleanups, self.func, *args, **kwargs)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/cleanup.py", line 165, in _do_with_cleanups
   result = func(*args, **kwargs)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/builtins.py", line 3788, in run
   result = tests.selftest(**selftest_kwargs)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/__init__.py", line 3709, in selftest
   result_decorators=result_decorators,
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/__init__.py", line 3217, in run_suite
   result = runner.run(suite)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/__init__.py", line 4758, in run
   test.run(result)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/__init__.py", line 3331, in run
   return super(CountingDecorator, self).run(result)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/__init__.py", line 3320, in run
   test.run(result)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/TestUtil.py", line 89, in run
   tests.pop().run(result)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/TestUtil.py", line 89, in run
   tests.pop().run(result)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/TestUtil.py", line 89, in run
   tests.pop().run(result)
 File "/usr/lib/python2.6/dist-packages/testtools/testcase.py", line 420, in run
   return self.__RunTest(self, self.exception_handlers).run(result)
 File "/usr/lib/python2.6/dist-packages/testtools/runtest.py", line 67, in run
   return self._run_one(actual_result)
 File "/usr/lib/python2.6/dist-packages/testtools/runtest.py", line 81, in _run_one
   return self._run_prepared_result(ExtendedToOriginalDecorator(result))
 File "/usr/lib/python2.6/dist-packages/testtools/runtest.py", line 100, in _run_prepared_result
   handler(self.case, self.result, e)
 File "/usr/lib/python2.6/dist-packages/testtools/testcase.py", line 388, in _report_error
   result.addError(self, details=self.getDetails())
 File "/usr/lib/python2.6/dist-packages/testtools/testresult/real.py", line 399, in addError
   return self.decorated.addError(test, details=details)
 File "/usr/lib/python2.6/dist-packages/subunit/test_results.py", line 118, in addError
   return self.super.addError(test, err, details=details)
 File "/usr/lib/python2.6/dist-packages/subunit/test_results.py", line 56, in add...

Read more...

Revision history for this message
Martin Packman (gz) wrote :

I'll have a go at reproducing that problem locally, the traceback isn't useful. Looks like a (probably not introduced by this branch) knock-on error from an error (probably due to this branch) on one of the tests involving non-ascii output.

Revision history for this message
Martin Packman (gz) wrote :

Reproduce with `./bzr selftest --subunit -s bt.test_trace mutter_never_fails` or remove --subunit to see the underlying error. Note also --parallel=fork doesn't break while --parallel=subprocess does so this is a real regression due to moving the stream wrapping around.

Revision history for this message
Martin Pool (mbp) wrote :

    def test_mutter_never_fails(self):
        # Even if the decode/encode stage fails, mutter should not
        # raise an exception
        # This test checks that mutter doesn't fail; the current behaviour
        # is that it doesn't fail *and writes non-utf8*.
        mutter(u'Writing a greek mu (\xb5) works in a unicode string')
        mutter('But fails in an ascii string \xb5')
        mutter('and in an ascii argument: %s', '\xb5')
        log = self.get_log()
        self.assertContainsRe(log, 'Writing a greek mu')
        self.assertContainsRe(log, "But fails in an ascii string")
        # However, the log content object does unicode replacement on reading
        # to let it get unicode back where good data has been written. So we
        # have to do a replaceent here as well.
        self.assertContainsRe(log, "ascii argument: \xb5".decode('utf8',
            'replace'))

So, this test is passing in the way the comment describes: mutter
doesn't actually fail, but we're no longer writing what we expected
to.

Looking at what got written, it's consistent with the currently
documented behaviour: lines sent as unicode get written as utf8 and
lines sent as byte strings are sent through as such (therefore the
file's then not valid utf8.) I think this is still reasonable
behaviour.

(Pdb) p log
'0.232 Writing a greek mu (\xc2\xb5) works in a unicode string\n0.232
 But fails in an ascii string \xb5\n0.232 and in an ascii argument:
\xb5\n'

The main change we'd need then, is that when looking at the result, we
should just search for a non-utf8 string.

Revision history for this message
Martin Pool (mbp) wrote :

I'm going to send this in; post-review welcome.

review: Approve
Revision history for this message
Martin Pool (mbp) wrote :

sent to pqm by email

Revision history for this message
Martin Pool (mbp) wrote :

failed pqm due to a merge conflict

lp:~mbp/bzr/test-errors updated
5979. By Martin Pool

merge trunk

Revision history for this message
Martin Pool (mbp) wrote :

sent to pqm by email

Revision history for this message
Martin Pool (mbp) wrote :
Download full text (4.2 KiB)

failed again with

bzr: ERROR: exceptions.UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 34: ordinal not in range(128)

Traceback (most recent call last):
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/commands.py", line 918, in exception_to_return_code
   return the_callable(*args, **kwargs)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/commands.py", line 1118, in run_bzr
   ret = run(*run_argv)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/commands.py", line 676, in run_argv_aliases
   return self.run(**all_cmd_args)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/commands.py", line 698, in run
   return self._operation.run_simple(*args, **kwargs)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/cleanup.py", line 135, in run_simple
   self.cleanups, self.func, *args, **kwargs)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/cleanup.py", line 165, in _do_with_cleanups
   result = func(*args, **kwargs)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/builtins.py", line 3836, in run
   result = tests.selftest(**selftest_kwargs)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/__init__.py", line 3735, in selftest
   result_decorators=result_decorators,
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/__init__.py", line 3243, in run_suite
   result = runner.run(suite)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/__init__.py", line 4477, in run
   test.run(result)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/__init__.py", line 3357, in run
   return super(CountingDecorator, self).run(result)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/__init__.py", line 3346, in run
   test.run(result)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/TestUtil.py", line 89, in run
   tests.pop().run(result)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/TestUtil.py", line 89, in run
   tests.pop().run(result)
 File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/TestUtil.py", line 89, in run
   tests.pop().run(result)
 File "/usr/lib/python2.6/dist-packages/testtools/testcase.py", line 420, in run
   return self.__RunTest(self, self.exception_handlers).run(result)
 File "/usr/lib/python2.6/dist-packages/testtools/runtest.py", line 67, in run
   return self._run_one(actual_result)
 File "/usr/lib/python2.6/dist-packages/testtools/runtest.py", line 81, in _run_one
   return self._run_prepared_result(ExtendedToOriginalDecorator(result))
 File "/usr/lib/python2.6/dist-packages/testtools/runtest.py", line 100, in _run_prepared_result
   handler(self.case, self.result, e)
 File "/usr/lib/python2.6/dist-packages/testtools/testcase.py", line 388, in _report_error
   result.addError(self, details=self.getDetails())
 File "/usr/lib/python2.6/dist-packages/testtools/testresult/real.py", line 399, in addError
   return self.decorated.addError(test, details=details)
 File "/usr/lib/python2.6/dist-packages/subunit/test_results.py", line 118, in addError
   return self.super.addError(test, err, details=details)
 File "/usr/lib/python2.6/dist-packages/subunit/test_results.py", line 56, in addError
   return self.decorated.addError(test, err, det...

Read more...

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 8/4/2011 2:34 AM, Martin Pool wrote:
> failed again with
...

> File "/usr/lib/python2.6/dist-packages/subunit/chunked.py", line
> 148, in flush self.output.write(''.join(buffered_bytes)) File
> "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/ui/text.py", line 540,
> in write self.wrapped_stream.write(to_write) File
> "/usr/lib/python2.6/codecs.py", line 351, in write data, consumed =
> self.encode(object, self.errors) UnicodeDecodeError: 'ascii' codec
> can't decode byte 0xc2 in position 34: ordinal not in range(128)
>

buffered_bytes sure looks like something that *should* be all byte
strings, and not Unicode at that point. I wonder how a Unicode bit snuck
in...

Or maybe it was properly encoded into bytes, but we are using a codec
wrapper. And writing bytes to a wrapper silently upcasts it to Unicode
before downcasting it in its own encoding back to bytes.

wrapped_stream seems risky with a "buffered_bytes" parameter.

John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk477QEACgkQJdeBCYSNAAMfHwCfW3sMTtzwp4W6X0ydSB/IpONo
fDIAoLR8PWxq+/PcsOtEwNYeN2PSIKyd
=vcS/
-----END PGP SIGNATURE-----

Unmerged revisions

5979. By Martin Pool

merge trunk

5978. By Martin Pool

Update test_mutter_never_fails to have more precise assertions and cope with getting non-ascii back

5977. By Martin Pool

Resolve conflicts

5976. By Martin Pool

Remove intentionally broken test

5975. By Martin Pool

Bugs in old subunits aren't bzr bugs

5974. By Martin Pool

Don't print traceback for xfail messages

5973. By Martin Pool

Unify code to report selftest failures

5972. By Martin Pool

Avoid excess indenting; handle unicode failures better in selftest -v

5971. By Martin Pool

Don't attach the log to the testtools object until the end when we know there are actual contents

5970. By Martin Pool

Send test output streams through the ui stream mechanism.

This gives them the default stream encoding, and should avoid clashing with progress bars.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/osutils.py'
--- bzrlib/osutils.py 2011-07-25 07:11:56 +0000
+++ bzrlib/osutils.py 2011-08-03 02:27:34 +0000
@@ -2319,8 +2319,17 @@
23192319
2320 def __init__(self, encode, stream, errors='strict'):2320 def __init__(self, encode, stream, errors='strict'):
2321 codecs.StreamWriter.__init__(self, stream, errors)2321 codecs.StreamWriter.__init__(self, stream, errors)
2322 self._underlying_stream = stream
2323 self._errors = errors
2322 self.encode = encode2324 self.encode = encode
23232325
2326 def __repr__(self):
2327 return '%s(%r, %r, %r)' % (
2328 self.__class__.__name__,
2329 self.encode,
2330 self._underlying_stream,
2331 self._errors)
2332
2324 def write(self, object):2333 def write(self, object):
2325 if type(object) is str:2334 if type(object) is str:
2326 self.stream.write(object)2335 self.stream.write(object)
@@ -2328,6 +2337,7 @@
2328 data, _ = self.encode(object, self.errors)2337 data, _ = self.encode(object, self.errors)
2329 self.stream.write(data)2338 self.stream.write(data)
23302339
2340
2331if sys.platform == 'win32':2341if sys.platform == 'win32':
2332 def open_file(filename, mode='r', bufsize=-1):2342 def open_file(filename, mode='r', bufsize=-1):
2333 """This function is used to override the ``open`` builtin.2343 """This function is used to override the ``open`` builtin.
23342344
=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py 2011-08-02 01:10:27 +0000
+++ bzrlib/tests/__init__.py 2011-08-03 02:27:34 +0000
@@ -268,17 +268,25 @@
268 self._first_thread_leaker_id = None268 self._first_thread_leaker_id = None
269 self._tests_leaking_threads_count = 0269 self._tests_leaking_threads_count = 0
270 self._traceback_from_test = None270 self._traceback_from_test = None
271 self.repeat_failures_at_end = False
271272
272 def stopTestRun(self):273 def stopTestRun(self):
273 run = self.testsRun274 run = self.testsRun
274 actionTaken = "Ran"275 actionTaken = "Ran"
275 stopTime = time.time()276 stopTime = time.time()
276 timeTaken = stopTime - self.startTime277 timeTaken = stopTime - self.startTime
277 # GZ 2010-07-19: Seems testtools has no printErrors method, and though278 if self.repeat_failures_at_end:
278 # the parent class method is similar have to duplicate279 # Normally, any failures or errors will be printed as they
279 self._show_list('ERROR', self.errors)280 # occur. (For example this lets you choose to interrupt the
280 self._show_list('FAIL', self.failures)281 # test run as soon as something fails, or to immediately start
281 self.stream.write(self.sep2)282 # debugging them while the test run continues.)
283 #
284 # GZ 2010-07-19: Seems testtools has no printErrors method, and
285 # though the parent class method is similar, we need duplicate
286 # code here.
287 self._show_list('ERROR', self.errors)
288 self._show_list('FAIL', self.failures)
289 self.stream.write(self.sep2)
282 self.stream.write("%s %d test%s in %.3fs\n\n" % (actionTaken,290 self.stream.write("%s %d test%s in %.3fs\n\n" % (actionTaken,
283 run, run != 1 and "s" or "", timeTaken))291 run, run != 1 and "s" or "", timeTaken))
284 if not self.wasSuccessful():292 if not self.wasSuccessful():
@@ -552,7 +560,18 @@
552 return self.wasSuccessful()560 return self.wasSuccessful()
553561
554562
555class TextTestResult(ExtendedTestResult):563class BaseTextTestResult(ExtendedTestResult):
564 """Common base for verbose and concise test results."""
565
566 def report_error_details(self, err):
567 # Normally called with a StringException, which has its whole
568 # traceback in its value.
569 self.stream.write(u'%s\n'
570 % err[1])
571
572
573
574class TextTestResult(BaseTextTestResult):
556 """Displays progress and results of tests in text form"""575 """Displays progress and results of tests in text form"""
557576
558 def __init__(self, stream, descriptions, verbosity,577 def __init__(self, stream, descriptions, verbosity,
@@ -621,22 +640,35 @@
621 return self._shortened_test_description(test)640 return self._shortened_test_description(test)
622641
623 def report_error(self, test, err):642 def report_error(self, test, err):
624 self.stream.write('ERROR: %s\n %s\n' % (643 """Report an unexpected error running a test.
625 self._test_description(test),644
626 err[1],645 :param test: TestCase instance.
627 ))646 :param err: An error-tuple, but typically a testtools
647 StringException with the whole traceback already baked into the
648 string form.
649 """
650 self.stream.write(
651 u'ERROR: %s\n' % self._test_description(test))
652 self.report_error_details(err)
628653
629 def report_failure(self, test, err):654 def report_failure(self, test, err):
630 self.stream.write('FAIL: %s\n %s\n' % (655 """Report a test failure.
631 self._test_description(test),656
632 err[1],657 :param test: TestCase instance.
633 ))658 :param err: An error-tuple, but typically a testtools
659 StringException with the whole traceback already baked into the
660 string form.
661 """
662 self.stream.write(
663 u'FAIL: %s\n' % self._test_description(test))
664 self.report_error_details(err)
634665
635 def report_known_failure(self, test, err):666 def report_known_failure(self, test, err):
636 pass667 pass
637668
638 def report_unexpected_success(self, test, reason):669 def report_unexpected_success(self, test, reason):
639 self.stream.write('FAIL: %s\n %s: %s\n' % (670 self.stream.write(
671 u'FAIL: %s\n%s: %s\n' % (
640 self._test_description(test),672 self._test_description(test),
641 "Unexpected success. Should have failed",673 "Unexpected success. Should have failed",
642 reason,674 reason,
@@ -652,7 +684,7 @@
652 """test cannot be run because feature is missing."""684 """test cannot be run because feature is missing."""
653685
654686
655class VerboseTestResult(ExtendedTestResult):687class VerboseTestResult(BaseTextTestResult):
656 """Produce long output, with one line per test run plus times"""688 """Produce long output, with one line per test run plus times"""
657689
658 def _ellipsize_to_right(self, a_string, final_width):690 def _ellipsize_to_right(self, a_string, final_width):
@@ -681,50 +713,59 @@
681 self.stream.flush()713 self.stream.flush()
682714
683 def _error_summary(self, err):715 def _error_summary(self, err):
684 indent = ' ' * 4716 return unicode(err[1])
685 return '%s%s' % (indent, err[1])
686717
687 def report_error(self, test, err):718 def report_error(self, test, err):
688 self.stream.write('ERROR %s\n%s\n'719 self.stream.write(u'ERROR %s\n'
689 % (self._testTimeString(test),720 % (self._testTimeString(test),))
690 self._error_summary(err)))721 self.report_error_details(err)
691722
692 def report_failure(self, test, err):723 def report_failure(self, test, err):
693 self.stream.write(' FAIL %s\n%s\n'724 self.stream.write(u' FAIL %s\n'
694 % (self._testTimeString(test),725 % (self._testTimeString(test)))
695 self._error_summary(err)))726 self.report_error_details(err)
696727
697 def report_known_failure(self, test, err):728 def report_known_failure(self, test, err):
698 self.stream.write('XFAIL %s\n%s\n'729 self.stream.write(u'XFAIL %s\n'
699 % (self._testTimeString(test),730 % (self._testTimeString(test)))
700 self._error_summary(err)))731 # If possible, just give the short reason; people who want to work
732 # on this test and get it going know where to find it.
733 getDetails = getattr(test, 'getDetails', None)
734 if getDetails is None:
735 reason = None
736 else:
737 reason = test.getDetails().get('reason')
738 if reason:
739 self.stream.write(u' %s\n' % (u''.join(reason.iter_text())))
740 else:
741 self.report_error_details(err)
701742
702 def report_unexpected_success(self, test, reason):743 def report_unexpected_success(self, test, reason):
703 self.stream.write(' FAIL %s\n%s: %s\n'744 self.stream.write(u' FAIL %s\n%s: %s\n'
704 % (self._testTimeString(test),745 % (self._testTimeString(test),
705 "Unexpected success. Should have failed",746 "Unexpected success. Should have failed",
706 reason))747 reason))
707748
708 def report_success(self, test):749 def report_success(self, test):
709 self.stream.write(' OK %s\n' % self._testTimeString(test))750 self.stream.write(u' OK %s\n' % self._testTimeString(test))
710 for bench_called, stats in getattr(test, '_benchcalls', []):751 for bench_called, stats in getattr(test, '_benchcalls', []):
711 self.stream.write('LSProf output for %s(%s, %s)\n' % bench_called)752 self.stream.write(u'LSProf output for %s(%s, %s)\n' % bench_called)
712 stats.pprint(file=self.stream)753 stats.pprint(file=self.stream)
713 # flush the stream so that we get smooth output. This verbose mode is754 # flush the stream so that we get smooth output. This verbose mode is
714 # used to show the output in PQM.755 # used to show the output in PQM.
715 self.stream.flush()756 self.stream.flush()
716757
717 def report_skip(self, test, reason):758 def report_skip(self, test, reason):
718 self.stream.write(' SKIP %s\n%s\n'759 self.stream.write(u' SKIP %s\n%s\n'
719 % (self._testTimeString(test), reason))760 % (self._testTimeString(test), reason))
720761
721 def report_not_applicable(self, test, reason):762 def report_not_applicable(self, test, reason):
722 self.stream.write(' N/A %s\n %s\n'763 self.stream.write(u' N/A %s\n %s\n'
723 % (self._testTimeString(test), reason))764 % (self._testTimeString(test), reason))
724765
725 def report_unsupported(self, test, feature):766 def report_unsupported(self, test, feature):
726 """test cannot be run because feature is missing."""767 """test cannot be run because feature is missing."""
727 self.stream.write("NODEP %s\n The feature '%s' is not available.\n"768 self.stream.write(u"NODEP %s\n The feature '%s' is not available.\n"
728 %(self._testTimeString(test), feature))769 %(self._testTimeString(test), feature))
729770
730771
@@ -732,12 +773,13 @@
732 stop_on_failure = False773 stop_on_failure = False
733774
734 def __init__(self,775 def __init__(self,
735 stream=sys.stderr,776 stream=None,
736 descriptions=0,777 descriptions=0,
737 verbosity=1,778 verbosity=1,
738 bench_history=None,779 bench_history=None,
739 strict=False,780 strict=False,
740 result_decorators=None,781 result_decorators=None,
782 output_encoding=None,
741 ):783 ):
742 """Create a TextTestRunner.784 """Create a TextTestRunner.
743785
@@ -746,22 +788,6 @@
746 applied left to right - the first element in the list is the 788 applied left to right - the first element in the list is the
747 innermost decorator.789 innermost decorator.
748 """790 """
749 # stream may know claim to know to write unicode strings, but in older
750 # pythons this goes sufficiently wrong that it is a bad idea. (
751 # specifically a built in file with encoding 'UTF-8' will still try
752 # to encode using ascii.
753 new_encoding = osutils.get_terminal_encoding()
754 codec = codecs.lookup(new_encoding)
755 if type(codec) is tuple:
756 # Python 2.4
757 encode = codec[0]
758 else:
759 encode = codec.encode
760 # GZ 2010-09-08: Really we don't want to be writing arbitrary bytes,
761 # so should swap to the plain codecs.StreamWriter
762 stream = osutils.UnicodeOrBytesToBytesWriter(encode, stream,
763 "backslashreplace")
764 stream.encoding = new_encoding
765 self.stream = stream791 self.stream = stream
766 self.descriptions = descriptions792 self.descriptions = descriptions
767 self.verbosity = verbosity793 self.verbosity = verbosity
@@ -1691,14 +1717,7 @@
16911717
1692 The file is removed as the test is torn down.1718 The file is removed as the test is torn down.
1693 """1719 """
1694 pseudo_log_file = StringIO()1720 self._log_file = StringIO()
1695 def _get_log_contents_for_weird_testtools_api():
1696 return [pseudo_log_file.getvalue().decode(
1697 "utf-8", "replace").encode("utf-8")]
1698 self.addDetail("log", content.Content(content.ContentType("text",
1699 "plain", {"charset": "utf8"}),
1700 _get_log_contents_for_weird_testtools_api))
1701 self._log_file = pseudo_log_file
1702 self._log_memento = trace.push_log_file(self._log_file)1721 self._log_memento = trace.push_log_file(self._log_file)
1703 self.addCleanup(self._finishLogFile)1722 self.addCleanup(self._finishLogFile)
17041723
@@ -1711,6 +1730,18 @@
1711 # flush the log file, to get all content1730 # flush the log file, to get all content
1712 trace._trace_file.flush()1731 trace._trace_file.flush()
1713 trace.pop_log_file(self._log_memento)1732 trace.pop_log_file(self._log_memento)
1733 log_contents = self.get_log()
1734 if not log_contents:
1735 return
1736 def _get_log_contents_for_weird_testtools_api():
1737 # testtools adds the file by being passed a function that returns
1738 # an iterable of chunks.
1739 return [log_contents.decode("utf-8", "replace")
1740 .encode("utf-8")]
1741 self.addDetail("log",
1742 content.Content(content.ContentType("text",
1743 "plain", {"charset": "utf8"}),
1744 _get_log_contents_for_weird_testtools_api))
17141745
1715 def thisFailsStrictLockCheck(self):1746 def thisFailsStrictLockCheck(self):
1716 """It is known that this test would fail with -Dstrict_locks.1747 """It is known that this test would fail with -Dstrict_locks.
@@ -1893,7 +1924,7 @@
18931924
1894 Undecodable characters are replaced.1925 Undecodable characters are replaced.
1895 """1926 """
1896 return u"".join(self.getDetails()['log'].iter_text())1927 return self._log_file.getvalue()
18971928
1898 def requireFeature(self, feature):1929 def requireFeature(self, feature):
1899 """This test requires a specific feature is available.1930 """This test requires a specific feature is available.
@@ -3175,8 +3206,6 @@
3175 verbosity = 13206 verbosity = 1
3176 if runner_class is None:3207 if runner_class is None:
3177 runner_class = TextTestRunner3208 runner_class = TextTestRunner
3178 if stream is None:
3179 stream = sys.stdout
3180 runner = runner_class(stream=stream,3209 runner = runner_class(stream=stream,
3181 descriptions=0,3210 descriptions=0,
3182 verbosity=verbosity,3211 verbosity=verbosity,
@@ -3605,6 +3634,36 @@
3605selftest_debug_flags = set()3634selftest_debug_flags = set()
36063635
36073636
3637def _output_stream_for_tests(stream=None, output_encoding=None):
3638 """Make a stream for test output that can safely encode anything.
3639
3640 This goes to 'stream' if it is specified (for testing), otherwise
3641 to the ui's output stream.
3642
3643 Specifically, on older Pythons a built in file with encoding 'UTF-8'
3644 will still try to encode using ascii.
3645 """
3646 if stream is None:
3647 stream = ui.ui_factory.make_output_stream(
3648 None, 'backslashreplace')
3649 else:
3650 if output_encoding is None:
3651 output_encoding = osutils.get_terminal_encoding()
3652 codec = codecs.lookup(output_encoding)
3653 if type(codec) is tuple:
3654 # Python 2.4
3655 encode = codec[0]
3656 else:
3657 encode = codec.encode
3658 # GZ 2010-09-08: Really we don't want to be writing arbitrary bytes,
3659 # so should swap to the plain codecs.StreamWriter, and fix up any
3660 # tests that do try to send bytes.
3661 stream = osutils.UnicodeOrBytesToBytesWriter(encode, stream,
3662 "backslashreplace")
3663 stream.encoding = output_encoding
3664 return stream
3665
3666
3608def selftest(verbose=False, pattern=".*", stop_on_failure=True,3667def selftest(verbose=False, pattern=".*", stop_on_failure=True,
3609 transport=None,3668 transport=None,
3610 test_suite_factory=None,3669 test_suite_factory=None,
@@ -3630,6 +3689,7 @@
3630 from bzrlib import repository3689 from bzrlib import repository
3631 repository._deprecation_warning_done = True3690 repository._deprecation_warning_done = True
36323691
3692 stream = _output_stream_for_tests(stream, None)
3633 global default_transport3693 global default_transport
3634 if transport is None:3694 if transport is None:
3635 transport = default_transport3695 transport = default_transport
36363696
=== modified file 'bzrlib/tests/test_selftest.py'
--- bzrlib/tests/test_selftest.py 2011-08-02 01:10:27 +0000
+++ bzrlib/tests/test_selftest.py 2011-08-03 02:27:34 +0000
@@ -79,20 +79,6 @@
79 return [t.id() for t in tests.iter_suite_tests(test_suite)]79 return [t.id() for t in tests.iter_suite_tests(test_suite)]
8080
8181
82class MetaTestLog(tests.TestCase):
83
84 def test_logging(self):
85 """Test logs are captured when a test fails."""
86 self.log('a test message')
87 details = self.getDetails()
88 log = details['log']
89 self.assertThat(log.content_type, Equals(ContentType(
90 "text", "plain", {"charset": "utf8"})))
91 self.assertThat(u"".join(log.iter_text()), Equals(self.get_log()))
92 self.assertThat(self.get_log(),
93 DocTestMatches(u"...a test message\n", doctest.ELLIPSIS))
94
95
96class TestTreeShape(tests.TestCaseInTempDir):82class TestTreeShape(tests.TestCaseInTempDir):
9783
98 def test_unicode_paths(self):84 def test_unicode_paths(self):
@@ -869,8 +855,8 @@
869 self.assertContainsRe(lines[0], r'XFAIL *\d+ms$')855 self.assertContainsRe(lines[0], r'XFAIL *\d+ms$')
870 if sys.version_info > (2, 7):856 if sys.version_info > (2, 7):
871 self.expectFailure("_ExpectedFailure on 2.7 loses the message",857 self.expectFailure("_ExpectedFailure on 2.7 loses the message",
872 self.assertNotEqual, lines[1], ' ')858 self.assertNotEqual, lines[1], '')
873 self.assertEqual(lines[1], ' foo')859 self.assertEqual(lines[1], 'foo')
874 self.assertEqual(2, len(lines))860 self.assertEqual(2, len(lines))
875861
876 def get_passing_test(self):862 def get_passing_test(self):
@@ -1025,11 +1011,13 @@
1025 class Test(tests.TestCase):1011 class Test(tests.TestCase):
1026 def known_failure_test(self):1012 def known_failure_test(self):
1027 self.expectFailure('failed', self.assertTrue, False)1013 self.expectFailure('failed', self.assertTrue, False)
1014
1015 def unexpected_failure_test(self):
1016 self.fail("foo")
1017
1028 test = unittest.TestSuite()1018 test = unittest.TestSuite()
1029 test.addTest(Test("known_failure_test"))1019 test.addTest(Test("known_failure_test"))
1030 def failing_test():1020 test.addTest(Test("unexpected_failure_test"))
1031 raise AssertionError('foo')
1032 test.addTest(unittest.FunctionTestCase(failing_test))
1033 stream = StringIO()1021 stream = StringIO()
1034 runner = tests.TextTestRunner(stream=stream)1022 runner = tests.TextTestRunner(stream=stream)
1035 result = self.run_test_runner(runner, test)1023 result = self.run_test_runner(runner, test)
@@ -1037,21 +1025,17 @@
1037 self.assertContainsRe(stream.getvalue(),1025 self.assertContainsRe(stream.getvalue(),
1038 '(?sm)^bzr selftest.*$'1026 '(?sm)^bzr selftest.*$'
1039 '.*'1027 '.*'
1040 '^======================================================================\n'1028 r'^FAIL: .*\.unexpected_failure_test'
1041 '^FAIL: failing_test\n'1029 '.*'
1042 '^----------------------------------------------------------------------\n'1030 r'Traceback \(most recent call last\):'
1043 'Traceback \\(most recent call last\\):\n'1031 '.*'
1044 ' .*' # File .*, line .*, in failing_test' - but maybe not from .pyc1032 '^AssertionError: foo'
1045 ' raise AssertionError\\(\'foo\'\\)\n'
1046 '.*'
1047 '^----------------------------------------------------------------------\n'
1048 '.*'1033 '.*'
1049 'FAILED \\(failures=1, known_failure_count=1\\)'1034 'FAILED \\(failures=1, known_failure_count=1\\)'
1050 )1035 )
10511036
1052 def test_known_failure_ok_run(self):1037 def test_known_failure_ok_run(self):
1053 # run a test that generates a known failure which should be printed in1038 """A test that generates a known failure is printed."""
1054 # the final output.
1055 class Test(tests.TestCase):1039 class Test(tests.TestCase):
1056 def known_failure_test(self):1040 def known_failure_test(self):
1057 self.knownFailure("Never works...")1041 self.knownFailure("Never works...")
@@ -1069,17 +1053,14 @@
1069 def test_unexpected_success_bad(self):1053 def test_unexpected_success_bad(self):
1070 class Test(tests.TestCase):1054 class Test(tests.TestCase):
1071 def test_truth(self):1055 def test_truth(self):
1072 self.expectFailure("No absolute truth", self.assertTrue, True)1056 self.expectFailure("No absolute truth", self.assertTrue,
1057 True)
1073 runner = tests.TextTestRunner(stream=StringIO())1058 runner = tests.TextTestRunner(stream=StringIO())
1074 result = self.run_test_runner(runner, Test("test_truth"))1059 result = self.run_test_runner(runner, Test("test_truth"))
1075 self.assertContainsRe(runner.stream.getvalue(),1060 self.assertContainsRe(runner.stream.getvalue(),
1076 "=+\n"1061 "(?ms)FAIL: \\S+\.test_truth\n"
1077 "FAIL: \\S+\.test_truth\n"1062 ".*No absolute truth\n"
1078 "-+\n"1063 ".*"
1079 "(?:.*\n)*"
1080 "No absolute truth\n"
1081 "(?:.*\n)*"
1082 "-+\n"
1083 "Ran 1 test in .*\n"1064 "Ran 1 test in .*\n"
1084 "\n"1065 "\n"
1085 "FAILED \\(failures=1\\)\n\\Z")1066 "FAILED \\(failures=1\\)\n\\Z")
@@ -1243,9 +1224,9 @@
1243 self.log(u"\u2606")1224 self.log(u"\u2606")
1244 self.fail("Now print that log!")1225 self.fail("Now print that log!")
1245 out = StringIO()1226 out = StringIO()
1246 self.overrideAttr(osutils, "get_terminal_encoding",1227 runner = tests.TextTestRunner(
1247 lambda trace=False: "ascii")1228 stream=tests._output_stream_for_tests(out, 'ascii'))
1248 result = self.run_test_runner(tests.TextTestRunner(stream=out),1229 result = self.run_test_runner(runner,
1249 FailureWithUnicode("test_log_unicode"))1230 FailureWithUnicode("test_log_unicode"))
1250 self.assertContainsRe(out.getvalue(),1231 self.assertContainsRe(out.getvalue(),
1251 "Text attachment: log\n"1232 "Text attachment: log\n"
@@ -1253,7 +1234,6 @@
1253 "\d+\.\d+ \\\\u2606\n"1234 "\d+\.\d+ \\\\u2606\n"
1254 "-+\n")1235 "-+\n")
12551236
1256
1257class SampleTestCase(tests.TestCase):1237class SampleTestCase(tests.TestCase):
12581238
1259 def _test_pass(self):1239 def _test_pass(self):
@@ -1698,6 +1678,9 @@
1698 mutter('this was a failing test')1678 mutter('this was a failing test')
1699 self.fail('this test will fail')1679 self.fail('this test will fail')
17001680
1681 def test_fail_nothing_logged(self):
1682 self.fail('just fail')
1683
1701 def test_error(self):1684 def test_error(self):
1702 mutter('this test errored')1685 mutter('this test errored')
1703 raise RuntimeError('gotcha')1686 raise RuntimeError('gotcha')
@@ -1732,6 +1715,13 @@
1732 test.run(result)1715 test.run(result)
1733 return result1716 return result
17341717
1718 def test_empty_log_not_attached(self):
1719 result = self._run_test('test_fail_nothing_logged')
1720 self.assertEqual(1, len(result.failures))
1721 result_content = result.failures[0][1]
1722 self.assertNotContainsRe(result_content,
1723 'Text attachment: log')
1724
1735 def test_fail_has_log(self):1725 def test_fail_has_log(self):
1736 result = self._run_test('test_fail')1726 result = self._run_test('test_fail')
1737 self.assertEqual(1, len(result.failures))1727 self.assertEqual(1, len(result.failures))
@@ -2190,13 +2180,13 @@
2190 content, result = self.run_subunit_stream('test_unexpected_success')2180 content, result = self.run_subunit_stream('test_unexpected_success')
2191 self.assertContainsRe(content, '(?m)^log$')2181 self.assertContainsRe(content, '(?m)^log$')
2192 self.assertContainsRe(content, 'test with unexpected success')2182 self.assertContainsRe(content, 'test with unexpected success')
2193 # GZ 2011-05-18: Old versions of subunit treat unexpected success as a2183 # GZ 2011-05-18: Old versions of subunit treat unexpected success as
2194 # success, if a min version check is added remove this2184 # a success, if a min version check is added remove this.
2195 from subunit import TestProtocolClient as _Client2185 from subunit import TestProtocolClient as _Client
2196 if _Client.addUnexpectedSuccess.im_func is _Client.addSuccess.im_func:2186 if _Client.addUnexpectedSuccess.im_func is _Client.addSuccess.im_func:
2197 self.expectFailure('subunit treats "unexpectedSuccess"'2187 # This isn't an expected failure in bzr, because there's nothing
2198 ' as a plain success',2188 # reasonable we can do about old buggy subunits.
2199 self.assertEqual, 1, len(result.unexpectedSuccesses))2189 self.skip("old subunit treats unexpectedSuccess as success")
2200 self.assertEqual(1, len(result.unexpectedSuccesses))2190 self.assertEqual(1, len(result.unexpectedSuccesses))
2201 test = result.unexpectedSuccesses[0]2191 test = result.unexpectedSuccesses[0]
2202 # RemotedTestCase doesn't preserve the "details"2192 # RemotedTestCase doesn't preserve the "details"
22032193
=== modified file 'bzrlib/tests/test_trace.py'
--- bzrlib/tests/test_trace.py 2011-05-27 20:46:01 +0000
+++ bzrlib/tests/test_trace.py 2011-08-03 02:27:34 +0000
@@ -238,21 +238,21 @@
238 self.assertEndsWith(log, ' "a string")\n')238 self.assertEndsWith(log, ' "a string")\n')
239239
240 def test_mutter_never_fails(self):240 def test_mutter_never_fails(self):
241 # Even if the decode/encode stage fails, mutter should not241 """Even if the decode/encode stage fails, mutter should not fail.
242 # raise an exception242
243 # This test checks that mutter doesn't fail; the current behaviour243 This test checks that mutter doesn't fail; the current behaviour
244 # is that it doesn't fail *and writes non-utf8*.244 is that it doesn't fail *and writes non-utf8*.
245 """
245 mutter(u'Writing a greek mu (\xb5) works in a unicode string')246 mutter(u'Writing a greek mu (\xb5) works in a unicode string')
246 mutter('But fails in an ascii string \xb5')247 mutter('But fails in a byte string \xb5')
247 mutter('and in an ascii argument: %s', '\xb5')248 mutter('and in a byte string argument: %s', '\xb5')
248 log = self.get_log()249 log = self.get_log()
249 self.assertContainsRe(log, 'Writing a greek mu')250 self.assertContainsRe(log, 'Writing a greek mu')
250 self.assertContainsRe(log, "But fails in an ascii string")251 self.assertContainsRe(log, "But fails in a byte string")
251 # However, the log content object does unicode replacement on reading252 # However, the log content object does unicode replacement on reading
252 # to let it get unicode back where good data has been written. So we253 # to let it get unicode back where good data has been written. So we
253 # have to do a replaceent here as well.254 # have to do a replaceent here as well.
254 self.assertContainsRe(log, "ascii argument: \xb5".decode('utf8',255 self.assertTrue('byte string argument: \xb5' in log)
255 'replace'))
256 256
257 def test_show_error(self):257 def test_show_error(self):
258 show_error('error1')258 show_error('error1')