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
1=== modified file 'bzrlib/osutils.py'
2--- bzrlib/osutils.py 2011-07-25 07:11:56 +0000
3+++ bzrlib/osutils.py 2011-08-03 02:27:34 +0000
4@@ -2319,8 +2319,17 @@
5
6 def __init__(self, encode, stream, errors='strict'):
7 codecs.StreamWriter.__init__(self, stream, errors)
8+ self._underlying_stream = stream
9+ self._errors = errors
10 self.encode = encode
11
12+ def __repr__(self):
13+ return '%s(%r, %r, %r)' % (
14+ self.__class__.__name__,
15+ self.encode,
16+ self._underlying_stream,
17+ self._errors)
18+
19 def write(self, object):
20 if type(object) is str:
21 self.stream.write(object)
22@@ -2328,6 +2337,7 @@
23 data, _ = self.encode(object, self.errors)
24 self.stream.write(data)
25
26+
27 if sys.platform == 'win32':
28 def open_file(filename, mode='r', bufsize=-1):
29 """This function is used to override the ``open`` builtin.
30
31=== modified file 'bzrlib/tests/__init__.py'
32--- bzrlib/tests/__init__.py 2011-08-02 01:10:27 +0000
33+++ bzrlib/tests/__init__.py 2011-08-03 02:27:34 +0000
34@@ -268,17 +268,25 @@
35 self._first_thread_leaker_id = None
36 self._tests_leaking_threads_count = 0
37 self._traceback_from_test = None
38+ self.repeat_failures_at_end = False
39
40 def stopTestRun(self):
41 run = self.testsRun
42 actionTaken = "Ran"
43 stopTime = time.time()
44 timeTaken = stopTime - self.startTime
45- # GZ 2010-07-19: Seems testtools has no printErrors method, and though
46- # the parent class method is similar have to duplicate
47- self._show_list('ERROR', self.errors)
48- self._show_list('FAIL', self.failures)
49- self.stream.write(self.sep2)
50+ if self.repeat_failures_at_end:
51+ # Normally, any failures or errors will be printed as they
52+ # occur. (For example this lets you choose to interrupt the
53+ # test run as soon as something fails, or to immediately start
54+ # debugging them while the test run continues.)
55+ #
56+ # GZ 2010-07-19: Seems testtools has no printErrors method, and
57+ # though the parent class method is similar, we need duplicate
58+ # code here.
59+ self._show_list('ERROR', self.errors)
60+ self._show_list('FAIL', self.failures)
61+ self.stream.write(self.sep2)
62 self.stream.write("%s %d test%s in %.3fs\n\n" % (actionTaken,
63 run, run != 1 and "s" or "", timeTaken))
64 if not self.wasSuccessful():
65@@ -552,7 +560,18 @@
66 return self.wasSuccessful()
67
68
69-class TextTestResult(ExtendedTestResult):
70+class BaseTextTestResult(ExtendedTestResult):
71+ """Common base for verbose and concise test results."""
72+
73+ def report_error_details(self, err):
74+ # Normally called with a StringException, which has its whole
75+ # traceback in its value.
76+ self.stream.write(u'%s\n'
77+ % err[1])
78+
79+
80+
81+class TextTestResult(BaseTextTestResult):
82 """Displays progress and results of tests in text form"""
83
84 def __init__(self, stream, descriptions, verbosity,
85@@ -621,22 +640,35 @@
86 return self._shortened_test_description(test)
87
88 def report_error(self, test, err):
89- self.stream.write('ERROR: %s\n %s\n' % (
90- self._test_description(test),
91- err[1],
92- ))
93+ """Report an unexpected error running a test.
94+
95+ :param test: TestCase instance.
96+ :param err: An error-tuple, but typically a testtools
97+ StringException with the whole traceback already baked into the
98+ string form.
99+ """
100+ self.stream.write(
101+ u'ERROR: %s\n' % self._test_description(test))
102+ self.report_error_details(err)
103
104 def report_failure(self, test, err):
105- self.stream.write('FAIL: %s\n %s\n' % (
106- self._test_description(test),
107- err[1],
108- ))
109+ """Report a test failure.
110+
111+ :param test: TestCase instance.
112+ :param err: An error-tuple, but typically a testtools
113+ StringException with the whole traceback already baked into the
114+ string form.
115+ """
116+ self.stream.write(
117+ u'FAIL: %s\n' % self._test_description(test))
118+ self.report_error_details(err)
119
120 def report_known_failure(self, test, err):
121 pass
122
123 def report_unexpected_success(self, test, reason):
124- self.stream.write('FAIL: %s\n %s: %s\n' % (
125+ self.stream.write(
126+ u'FAIL: %s\n%s: %s\n' % (
127 self._test_description(test),
128 "Unexpected success. Should have failed",
129 reason,
130@@ -652,7 +684,7 @@
131 """test cannot be run because feature is missing."""
132
133
134-class VerboseTestResult(ExtendedTestResult):
135+class VerboseTestResult(BaseTextTestResult):
136 """Produce long output, with one line per test run plus times"""
137
138 def _ellipsize_to_right(self, a_string, final_width):
139@@ -681,50 +713,59 @@
140 self.stream.flush()
141
142 def _error_summary(self, err):
143- indent = ' ' * 4
144- return '%s%s' % (indent, err[1])
145+ return unicode(err[1])
146
147 def report_error(self, test, err):
148- self.stream.write('ERROR %s\n%s\n'
149- % (self._testTimeString(test),
150- self._error_summary(err)))
151+ self.stream.write(u'ERROR %s\n'
152+ % (self._testTimeString(test),))
153+ self.report_error_details(err)
154
155 def report_failure(self, test, err):
156- self.stream.write(' FAIL %s\n%s\n'
157- % (self._testTimeString(test),
158- self._error_summary(err)))
159+ self.stream.write(u' FAIL %s\n'
160+ % (self._testTimeString(test)))
161+ self.report_error_details(err)
162
163 def report_known_failure(self, test, err):
164- self.stream.write('XFAIL %s\n%s\n'
165- % (self._testTimeString(test),
166- self._error_summary(err)))
167+ self.stream.write(u'XFAIL %s\n'
168+ % (self._testTimeString(test)))
169+ # If possible, just give the short reason; people who want to work
170+ # on this test and get it going know where to find it.
171+ getDetails = getattr(test, 'getDetails', None)
172+ if getDetails is None:
173+ reason = None
174+ else:
175+ reason = test.getDetails().get('reason')
176+ if reason:
177+ self.stream.write(u' %s\n' % (u''.join(reason.iter_text())))
178+ else:
179+ self.report_error_details(err)
180
181 def report_unexpected_success(self, test, reason):
182- self.stream.write(' FAIL %s\n%s: %s\n'
183+ self.stream.write(u' FAIL %s\n%s: %s\n'
184 % (self._testTimeString(test),
185 "Unexpected success. Should have failed",
186 reason))
187
188 def report_success(self, test):
189- self.stream.write(' OK %s\n' % self._testTimeString(test))
190+ self.stream.write(u' OK %s\n' % self._testTimeString(test))
191 for bench_called, stats in getattr(test, '_benchcalls', []):
192- self.stream.write('LSProf output for %s(%s, %s)\n' % bench_called)
193+ self.stream.write(u'LSProf output for %s(%s, %s)\n' % bench_called)
194 stats.pprint(file=self.stream)
195 # flush the stream so that we get smooth output. This verbose mode is
196 # used to show the output in PQM.
197 self.stream.flush()
198
199 def report_skip(self, test, reason):
200- self.stream.write(' SKIP %s\n%s\n'
201+ self.stream.write(u' SKIP %s\n%s\n'
202 % (self._testTimeString(test), reason))
203
204 def report_not_applicable(self, test, reason):
205- self.stream.write(' N/A %s\n %s\n'
206+ self.stream.write(u' N/A %s\n %s\n'
207 % (self._testTimeString(test), reason))
208
209 def report_unsupported(self, test, feature):
210 """test cannot be run because feature is missing."""
211- self.stream.write("NODEP %s\n The feature '%s' is not available.\n"
212+ self.stream.write(u"NODEP %s\n The feature '%s' is not available.\n"
213 %(self._testTimeString(test), feature))
214
215
216@@ -732,12 +773,13 @@
217 stop_on_failure = False
218
219 def __init__(self,
220- stream=sys.stderr,
221+ stream=None,
222 descriptions=0,
223 verbosity=1,
224 bench_history=None,
225 strict=False,
226 result_decorators=None,
227+ output_encoding=None,
228 ):
229 """Create a TextTestRunner.
230
231@@ -746,22 +788,6 @@
232 applied left to right - the first element in the list is the
233 innermost decorator.
234 """
235- # stream may know claim to know to write unicode strings, but in older
236- # pythons this goes sufficiently wrong that it is a bad idea. (
237- # specifically a built in file with encoding 'UTF-8' will still try
238- # to encode using ascii.
239- new_encoding = osutils.get_terminal_encoding()
240- codec = codecs.lookup(new_encoding)
241- if type(codec) is tuple:
242- # Python 2.4
243- encode = codec[0]
244- else:
245- encode = codec.encode
246- # GZ 2010-09-08: Really we don't want to be writing arbitrary bytes,
247- # so should swap to the plain codecs.StreamWriter
248- stream = osutils.UnicodeOrBytesToBytesWriter(encode, stream,
249- "backslashreplace")
250- stream.encoding = new_encoding
251 self.stream = stream
252 self.descriptions = descriptions
253 self.verbosity = verbosity
254@@ -1691,14 +1717,7 @@
255
256 The file is removed as the test is torn down.
257 """
258- pseudo_log_file = StringIO()
259- def _get_log_contents_for_weird_testtools_api():
260- return [pseudo_log_file.getvalue().decode(
261- "utf-8", "replace").encode("utf-8")]
262- self.addDetail("log", content.Content(content.ContentType("text",
263- "plain", {"charset": "utf8"}),
264- _get_log_contents_for_weird_testtools_api))
265- self._log_file = pseudo_log_file
266+ self._log_file = StringIO()
267 self._log_memento = trace.push_log_file(self._log_file)
268 self.addCleanup(self._finishLogFile)
269
270@@ -1711,6 +1730,18 @@
271 # flush the log file, to get all content
272 trace._trace_file.flush()
273 trace.pop_log_file(self._log_memento)
274+ log_contents = self.get_log()
275+ if not log_contents:
276+ return
277+ def _get_log_contents_for_weird_testtools_api():
278+ # testtools adds the file by being passed a function that returns
279+ # an iterable of chunks.
280+ return [log_contents.decode("utf-8", "replace")
281+ .encode("utf-8")]
282+ self.addDetail("log",
283+ content.Content(content.ContentType("text",
284+ "plain", {"charset": "utf8"}),
285+ _get_log_contents_for_weird_testtools_api))
286
287 def thisFailsStrictLockCheck(self):
288 """It is known that this test would fail with -Dstrict_locks.
289@@ -1893,7 +1924,7 @@
290
291 Undecodable characters are replaced.
292 """
293- return u"".join(self.getDetails()['log'].iter_text())
294+ return self._log_file.getvalue()
295
296 def requireFeature(self, feature):
297 """This test requires a specific feature is available.
298@@ -3175,8 +3206,6 @@
299 verbosity = 1
300 if runner_class is None:
301 runner_class = TextTestRunner
302- if stream is None:
303- stream = sys.stdout
304 runner = runner_class(stream=stream,
305 descriptions=0,
306 verbosity=verbosity,
307@@ -3605,6 +3634,36 @@
308 selftest_debug_flags = set()
309
310
311+def _output_stream_for_tests(stream=None, output_encoding=None):
312+ """Make a stream for test output that can safely encode anything.
313+
314+ This goes to 'stream' if it is specified (for testing), otherwise
315+ to the ui's output stream.
316+
317+ Specifically, on older Pythons a built in file with encoding 'UTF-8'
318+ will still try to encode using ascii.
319+ """
320+ if stream is None:
321+ stream = ui.ui_factory.make_output_stream(
322+ None, 'backslashreplace')
323+ else:
324+ if output_encoding is None:
325+ output_encoding = osutils.get_terminal_encoding()
326+ codec = codecs.lookup(output_encoding)
327+ if type(codec) is tuple:
328+ # Python 2.4
329+ encode = codec[0]
330+ else:
331+ encode = codec.encode
332+ # GZ 2010-09-08: Really we don't want to be writing arbitrary bytes,
333+ # so should swap to the plain codecs.StreamWriter, and fix up any
334+ # tests that do try to send bytes.
335+ stream = osutils.UnicodeOrBytesToBytesWriter(encode, stream,
336+ "backslashreplace")
337+ stream.encoding = output_encoding
338+ return stream
339+
340+
341 def selftest(verbose=False, pattern=".*", stop_on_failure=True,
342 transport=None,
343 test_suite_factory=None,
344@@ -3630,6 +3689,7 @@
345 from bzrlib import repository
346 repository._deprecation_warning_done = True
347
348+ stream = _output_stream_for_tests(stream, None)
349 global default_transport
350 if transport is None:
351 transport = default_transport
352
353=== modified file 'bzrlib/tests/test_selftest.py'
354--- bzrlib/tests/test_selftest.py 2011-08-02 01:10:27 +0000
355+++ bzrlib/tests/test_selftest.py 2011-08-03 02:27:34 +0000
356@@ -79,20 +79,6 @@
357 return [t.id() for t in tests.iter_suite_tests(test_suite)]
358
359
360-class MetaTestLog(tests.TestCase):
361-
362- def test_logging(self):
363- """Test logs are captured when a test fails."""
364- self.log('a test message')
365- details = self.getDetails()
366- log = details['log']
367- self.assertThat(log.content_type, Equals(ContentType(
368- "text", "plain", {"charset": "utf8"})))
369- self.assertThat(u"".join(log.iter_text()), Equals(self.get_log()))
370- self.assertThat(self.get_log(),
371- DocTestMatches(u"...a test message\n", doctest.ELLIPSIS))
372-
373-
374 class TestTreeShape(tests.TestCaseInTempDir):
375
376 def test_unicode_paths(self):
377@@ -869,8 +855,8 @@
378 self.assertContainsRe(lines[0], r'XFAIL *\d+ms$')
379 if sys.version_info > (2, 7):
380 self.expectFailure("_ExpectedFailure on 2.7 loses the message",
381- self.assertNotEqual, lines[1], ' ')
382- self.assertEqual(lines[1], ' foo')
383+ self.assertNotEqual, lines[1], '')
384+ self.assertEqual(lines[1], 'foo')
385 self.assertEqual(2, len(lines))
386
387 def get_passing_test(self):
388@@ -1025,11 +1011,13 @@
389 class Test(tests.TestCase):
390 def known_failure_test(self):
391 self.expectFailure('failed', self.assertTrue, False)
392+
393+ def unexpected_failure_test(self):
394+ self.fail("foo")
395+
396 test = unittest.TestSuite()
397 test.addTest(Test("known_failure_test"))
398- def failing_test():
399- raise AssertionError('foo')
400- test.addTest(unittest.FunctionTestCase(failing_test))
401+ test.addTest(Test("unexpected_failure_test"))
402 stream = StringIO()
403 runner = tests.TextTestRunner(stream=stream)
404 result = self.run_test_runner(runner, test)
405@@ -1037,21 +1025,17 @@
406 self.assertContainsRe(stream.getvalue(),
407 '(?sm)^bzr selftest.*$'
408 '.*'
409- '^======================================================================\n'
410- '^FAIL: failing_test\n'
411- '^----------------------------------------------------------------------\n'
412- 'Traceback \\(most recent call last\\):\n'
413- ' .*' # File .*, line .*, in failing_test' - but maybe not from .pyc
414- ' raise AssertionError\\(\'foo\'\\)\n'
415- '.*'
416- '^----------------------------------------------------------------------\n'
417+ r'^FAIL: .*\.unexpected_failure_test'
418+ '.*'
419+ r'Traceback \(most recent call last\):'
420+ '.*'
421+ '^AssertionError: foo'
422 '.*'
423 'FAILED \\(failures=1, known_failure_count=1\\)'
424 )
425
426 def test_known_failure_ok_run(self):
427- # run a test that generates a known failure which should be printed in
428- # the final output.
429+ """A test that generates a known failure is printed."""
430 class Test(tests.TestCase):
431 def known_failure_test(self):
432 self.knownFailure("Never works...")
433@@ -1069,17 +1053,14 @@
434 def test_unexpected_success_bad(self):
435 class Test(tests.TestCase):
436 def test_truth(self):
437- self.expectFailure("No absolute truth", self.assertTrue, True)
438+ self.expectFailure("No absolute truth", self.assertTrue,
439+ True)
440 runner = tests.TextTestRunner(stream=StringIO())
441 result = self.run_test_runner(runner, Test("test_truth"))
442 self.assertContainsRe(runner.stream.getvalue(),
443- "=+\n"
444- "FAIL: \\S+\.test_truth\n"
445- "-+\n"
446- "(?:.*\n)*"
447- "No absolute truth\n"
448- "(?:.*\n)*"
449- "-+\n"
450+ "(?ms)FAIL: \\S+\.test_truth\n"
451+ ".*No absolute truth\n"
452+ ".*"
453 "Ran 1 test in .*\n"
454 "\n"
455 "FAILED \\(failures=1\\)\n\\Z")
456@@ -1243,9 +1224,9 @@
457 self.log(u"\u2606")
458 self.fail("Now print that log!")
459 out = StringIO()
460- self.overrideAttr(osutils, "get_terminal_encoding",
461- lambda trace=False: "ascii")
462- result = self.run_test_runner(tests.TextTestRunner(stream=out),
463+ runner = tests.TextTestRunner(
464+ stream=tests._output_stream_for_tests(out, 'ascii'))
465+ result = self.run_test_runner(runner,
466 FailureWithUnicode("test_log_unicode"))
467 self.assertContainsRe(out.getvalue(),
468 "Text attachment: log\n"
469@@ -1253,7 +1234,6 @@
470 "\d+\.\d+ \\\\u2606\n"
471 "-+\n")
472
473-
474 class SampleTestCase(tests.TestCase):
475
476 def _test_pass(self):
477@@ -1698,6 +1678,9 @@
478 mutter('this was a failing test')
479 self.fail('this test will fail')
480
481+ def test_fail_nothing_logged(self):
482+ self.fail('just fail')
483+
484 def test_error(self):
485 mutter('this test errored')
486 raise RuntimeError('gotcha')
487@@ -1732,6 +1715,13 @@
488 test.run(result)
489 return result
490
491+ def test_empty_log_not_attached(self):
492+ result = self._run_test('test_fail_nothing_logged')
493+ self.assertEqual(1, len(result.failures))
494+ result_content = result.failures[0][1]
495+ self.assertNotContainsRe(result_content,
496+ 'Text attachment: log')
497+
498 def test_fail_has_log(self):
499 result = self._run_test('test_fail')
500 self.assertEqual(1, len(result.failures))
501@@ -2190,13 +2180,13 @@
502 content, result = self.run_subunit_stream('test_unexpected_success')
503 self.assertContainsRe(content, '(?m)^log$')
504 self.assertContainsRe(content, 'test with unexpected success')
505- # GZ 2011-05-18: Old versions of subunit treat unexpected success as a
506- # success, if a min version check is added remove this
507+ # GZ 2011-05-18: Old versions of subunit treat unexpected success as
508+ # a success, if a min version check is added remove this.
509 from subunit import TestProtocolClient as _Client
510 if _Client.addUnexpectedSuccess.im_func is _Client.addSuccess.im_func:
511- self.expectFailure('subunit treats "unexpectedSuccess"'
512- ' as a plain success',
513- self.assertEqual, 1, len(result.unexpectedSuccesses))
514+ # This isn't an expected failure in bzr, because there's nothing
515+ # reasonable we can do about old buggy subunits.
516+ self.skip("old subunit treats unexpectedSuccess as success")
517 self.assertEqual(1, len(result.unexpectedSuccesses))
518 test = result.unexpectedSuccesses[0]
519 # RemotedTestCase doesn't preserve the "details"
520
521=== modified file 'bzrlib/tests/test_trace.py'
522--- bzrlib/tests/test_trace.py 2011-05-27 20:46:01 +0000
523+++ bzrlib/tests/test_trace.py 2011-08-03 02:27:34 +0000
524@@ -238,21 +238,21 @@
525 self.assertEndsWith(log, ' "a string")\n')
526
527 def test_mutter_never_fails(self):
528- # Even if the decode/encode stage fails, mutter should not
529- # raise an exception
530- # This test checks that mutter doesn't fail; the current behaviour
531- # is that it doesn't fail *and writes non-utf8*.
532+ """Even if the decode/encode stage fails, mutter should not fail.
533+
534+ This test checks that mutter doesn't fail; the current behaviour
535+ is that it doesn't fail *and writes non-utf8*.
536+ """
537 mutter(u'Writing a greek mu (\xb5) works in a unicode string')
538- mutter('But fails in an ascii string \xb5')
539- mutter('and in an ascii argument: %s', '\xb5')
540+ mutter('But fails in a byte string \xb5')
541+ mutter('and in a byte string argument: %s', '\xb5')
542 log = self.get_log()
543 self.assertContainsRe(log, 'Writing a greek mu')
544- self.assertContainsRe(log, "But fails in an ascii string")
545+ self.assertContainsRe(log, "But fails in a byte string")
546 # However, the log content object does unicode replacement on reading
547 # to let it get unicode back where good data has been written. So we
548 # have to do a replaceent here as well.
549- self.assertContainsRe(log, "ascii argument: \xb5".decode('utf8',
550- 'replace'))
551+ self.assertTrue('byte string argument: \xb5' in log)
552
553 def test_show_error(self):
554 show_error('error1')