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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Pool | Approve | ||
Martin Packman (community) | Needs Information | ||
John A Meinel | Approve | ||
Review via email:
|
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.)
* UnicodeOrBytesT
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.
* Bugs in old subunits do not deserve a 'known failure' in bzr. Only
things we could fix (without time travel) should get that.

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

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_
My understanding of the proposed flag was it would be either during *or* at the end, never both.
+class BaseTextTestRes
Why a new class rather than just putting the method you want to share on ExtendedTestResult?
+ u'ERROR: %s\n' % self._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(
...
+ 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.

Martin Pool (mbp) wrote : | # |
test_trace.
lost connection during error report of test 'bzrlib.
thanks for the review comments

Martin Pool (mbp) 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_
>
> 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:/
>
> +class BaseTextTestRes
>
> 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_
>
> 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(
> ...
> + 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...

Martin Pool (mbp) wrote : | # |
This fails in pqm with some kind of unicode error in subunit integration:
bzr: ERROR: exceptions.
Traceback (most recent call last):
File "/home/
return the_callable(*args, **kwargs)
File "/home/
ret = run(*run_argv)
File "/home/
return self.run(
File "/home/
return self._operation
File "/home/
self.cleanups, self.func, *args, **kwargs)
File "/home/
result = func(*args, **kwargs)
File "/home/
result = tests.selftest(
File "/home/
result_
File "/home/
result = runner.run(suite)
File "/home/
test.run(result)
File "/home/
return super(CountingD
File "/home/
test.run(result)
File "/home/
tests.
File "/home/
tests.
File "/home/
tests.
File "/usr/lib/
return self.__
File "/usr/lib/
return self._run_
File "/usr/lib/
return self._run_
File "/usr/lib/
handler(
File "/usr/lib/
result.
File "/usr/lib/
return self.decorated.
File "/usr/lib/
return self.super.
File "/usr/lib/

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.

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=

Martin Pool (mbp) wrote : | # |
def test_mutter_
# 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('But fails in an ascii string \xb5')
mutter('and in an ascii argument: %s', '\xb5')
log = self.get_log()
# 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.
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.

Martin Pool (mbp) wrote : | # |
I'm going to send this in; post-review welcome.

Martin Pool (mbp) wrote : | # |
failed pqm due to a merge conflict
- 5979. By Martin Pool
-
merge trunk

Martin Pool (mbp) wrote : | # |
failed again with
bzr: ERROR: exceptions.
Traceback (most recent call last):
File "/home/
return the_callable(*args, **kwargs)
File "/home/
ret = run(*run_argv)
File "/home/
return self.run(
File "/home/
return self._operation
File "/home/
self.cleanups, self.func, *args, **kwargs)
File "/home/
result = func(*args, **kwargs)
File "/home/
result = tests.selftest(
File "/home/
result_
File "/home/
result = runner.run(suite)
File "/home/
test.run(result)
File "/home/
return super(CountingD
File "/home/
test.run(result)
File "/home/
tests.
File "/home/
tests.
File "/home/
tests.
File "/usr/lib/
return self.__
File "/usr/lib/
return self._run_
File "/usr/lib/
return self._run_
File "/usr/lib/
handler(
File "/usr/lib/
result.
File "/usr/lib/
return self.decorated.
File "/usr/lib/
return self.super.
File "/usr/lib/
return self.decorated.

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/
> 148, in flush self.output.
> "/home/
> in write self.wrapped_
> "/usr/lib/
> 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://
iEYEARECAAYFAk4
fDIAoLR8PWxq+
=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
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') |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
On 6/14/2011 5:51 AM, Martin Pool wrote: /code.launchpad .net/~mbp/ bzr/test- errors/ +merge/ 64485 oBytesWriter had the somewhat confusing behavior of
> Martin Pool has proposed merging lp:~mbp/bzr/test-errors into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> For more details, see:
> https:/
>
> 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.)
>
> * UnicodeOrBytesT
> 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!
> get_log( )).
> * 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.
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 enigmail. mozdev. org/
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAk3 3eXgACgkQJdeBCY SNAAMXzgCaAik/ 3rhrICt85tsgBYV f584Z xGIQYtT2GU85REi kDc
hGUAoNC7d66TJ+
=hh8p
-----END PGP SIGNATURE-----