Merge lp:~vila/bzr/321320-isolate-doc-tests into lp:bzr

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: 5583
Proposed branch: lp:~vila/bzr/321320-isolate-doc-tests
Merge into: lp:bzr
Prerequisite: lp:~vila/bzr/cleanup
Diff against target: 395 lines (+244/-63)
4 files modified
bzrlib/tests/__init__.py (+110/-58)
bzrlib/tests/test_selftest.py (+122/-3)
doc/developers/testing.txt (+5/-2)
doc/en/release-notes/bzr-2.3.txt (+7/-0)
To merge this branch: bzr merge lp:~vila/bzr/321320-isolate-doc-tests
Reviewer Review Type Date Requested Status
Andrew Bennetts Needs Fixing
Review via email: mp+44230@code.launchpad.net

Commit message

Isolate doctests from os.environ

Description of the change

This isolates doctests from os.environ, fixing bug #321320.

We have a set of variables that we want to be isolated from the external os.environ.

I moved them out of 'Test_case._cleanEnvironment' into a dedicated 'isolated_environ' to be able to reuse them.

I then created a override_os_environ/restore_environ pair of *functions* so I can use them as parameter to the DocTestSuite *function* (yes, it's not a class). These functions are then added to the doctest (not to the suite) as a setUp/tearDown pair.

I've thought about being even more radical here by modifying the selftest *command* to save/restore os.environ so that tests can just start with an empty os.environ and install whatever they need, but there are several problems with this approach:
- other test runners won't support it,
- this may trigger weird issues with lazy imports (what if the import is sometimes done before the os.environ is clean and sometimes not, etc),
- this would make it harder to write tests for this code (note that the tests added here rely on being properly isolated. I've commented heavily on that as it's easy to overlook).

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Some quick notes without getting into a proper review:

Main concern is that I think the doctests need more of bzrlib.tests.TestCase.setUp to be properly isolated, not just the _cleanEnvironment call, or perhaps even TestCaseWithMemoryTransport.setUp equivalent. This would mean pulling a lot more code off the TestCase class so it could be shared. I'm particularly thinking of TestCase._clear_hooks and similar things. Not having cleanUp usable from doctest setUp without clever hacks is particularly annoying.

+ 'http_proxy': None,
+ 'HTTP_PROXY': None,

Scope creepitis: I'd much prefer this was handled by the osutils.set_or_unset_env or similar, platforms without case sensitive environments shouldn't have to do extra work.

Revision history for this message
Vincent Ladeuil (vila) wrote :

> Some quick notes without getting into a proper review:
>
> Main concern is that I think the doctests need more of
> bzrlib.tests.TestCase.setUp to be properly isolated, not just the
> _cleanEnvironment call, or perhaps even TestCaseWithMemoryTransport.setUp
> equivalent. This would mean pulling a lot more code off the TestCase class so
> it could be shared. I'm particularly thinking of TestCase._clear_hooks and
> similar things. Not having cleanUp usable from doctest setUp without clever
> hacks is particularly annoying.

You wouldn't be able to call it from a doctest isn't it ?

But even then, DocTestSuite is a *function* which uses DocTestCase (the class) internally, I'm not against the principle of rewriting python's doctest, but I don't think it's in bzr scope :)

>
>
> + 'http_proxy': None,
> + 'HTTP_PROXY': None,
>
> Scope creepitis: I'd much prefer this was handled by the
> osutils.set_or_unset_env or similar, platforms without case sensitive
> environments shouldn't have to do extra work.

But it's handled this way because on OSes with case sensitive envs, both can be (and are) used !

Revision history for this message
Andrew Bennetts (spiv) wrote :

17 + 'HOME': os.getcwd(),

There's a small behaviour difference here, although I think it's probably ok: this calculates the isolated $HOME once, at module import time, rather that during each test's setUp. Given that tests that care about this probably use TestCaseWithMemoryTransport, which explicitly overrides HOME appropriately, it's probably ok... I wonder a little about the effect on doctests that might be influenced by $HOME that can't use TestCaseWithMemoryTransport. In fact, isn't that what bug 312320 is about? Does this really allow bzrlib.branchbuilder's doctests to run in isolation from whatever the user has in their ~/.bazaar? I can't see how it does.

194 + doc_suite = DocTestSuite(
195 + mod, optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)

That's a bit odd: shouldn't both DocTestSuite and REPORT_ONLY_FIRST_FAILURE be prefixed with “doctest.”, or neither of them?

240 + Since we use tests that are already isolated from os.environ abit of care

Typo: “a bit” is two words.

review: Needs Information
Revision history for this message
Andrew Bennetts (spiv) wrote :

My suspicion was right: this branch does not appear to fix 321320, except sometimes by accident.

If you e.g. run 'bzr selftest' from your home dir, HOME is set to your real home, and config values in your normal ~/.bazaar/bazaar.conf like create_signatures take effect. In my case that cases gnome-gpg to be run for every commit the test suite does. At best that will slow down the tests considerably, at worst cause test hangs or test failures. This case definitely ought to work, and if bug 321320 is properly fixed, it would.

review: Needs Fixing
Revision history for this message
Vincent Ladeuil (vila) wrote :

Nice catch !

Thinking more about it, I think the issue is that giving a value to a variable via isolated_environ is just wrong.

If set HOME to None there (but still set it to os.getcwd() for TestCase), I get no failures. I didn't dare before but this makes sense. A test that *requires* HOME to be set for whatever reason, should make sure it's set or fail.

Re-running the test suite without setting HOME *at all* ....<drum roll> pass !

The other variables set here are:

- EMAIL: root cause for the branchbuilder doctest failure, this one is under discussion about whether the *feature* of making whoami mandatory is a good one. Setting it here masked the issue and *users* faced the consequences, I'll leave it for now, but I'm convinced we should set it to None and let the related tests themselves care about it (including the currently hidden failures),
- TERM, LINES, COLUMNS, BZR_COLUMNS: again, the tests needing these settings should set these variables explicitly, we'll get a better tracking of which tests really care about them.

That being said, I'll investigate further and submit a more robust solution, two reviewers agreeing on the same issue is worth addressing isn't it ? :D

Revision history for this message
Vincent Ladeuil (vila) wrote :

So, it turns out that deleting HOME from the environment doesn't
annoy our test suite even slightly, so I did just that. I then
run this branch on babune to see how our various supported OSes
react against homeless tests and the result is: nothing. Not a
single test failed, much to my surprise I should say.

But once you accept that indeed our code and our tests don't need
it, it feels cleaner (and should address spiv's concerns about
GPG, if it doesn't, yell !).

Note that we still have tests that write to $HOME/.bzr.log
(better tracked by running with 'BZR_HOME=. ./bzr selftest'), but
they aren't doctests and as such are out of scope for this fix.

If someone (mgz ?) really feels strongly about adding more
isolation to doctests then feel free to do so, I've prepare
things a bit by adding two functions isolated_doctest_setUp and
isolated_doctest_tearDown that accept a 'test' parameter so it
should be possible to force them to run in a tmpdir.

But since there wasn't any leak I could find that would benefit
from such an isolation, I stopped there.

Now, to come back to spiv's first comment:
> My suspicion was right: this branch does not appear to fix 321320, except sometimes by accident.

> If you e.g. run 'bzr selftest' from your home dir, HOME is set to your real home, and config values in your normal ~/.bazaar/bazaar.conf like create_signatures take effect. In my case that cases gnome-gpg to be run for every commit the test suite does. At best that will slow down the tests considerably, at worst cause test hangs or test failures. This case definitely ought to work, and if bug 321320 is properly fixed, it would.

Would you mind checking this behaviour with bzr trunk ?

And is it fixed now ?

If it isn't, can you point me to the *doctest* you think is exhibiting this behaviour ?

Revision history for this message
Robert Collins (lifeless) wrote :

I suggest using a fixture (lp:python-fixtures) or similar rather than
a pair of functions. Its a bit cleaner and will be less code
(particularly if you reuse Fixture rather than rolling your own).

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

> If someone (mgz ?) really feels strongly about adding more
> isolation to doctests then feel free to do so, I've prepare
> things a bit by adding two functions isolated_doctest_setUp and
> isolated_doctest_tearDown that accept a 'test' parameter so it
> should be possible to force them to run in a tmpdir.

This may as well land if it fixes the problems with doctests people are actually having, even if it's not a complete solution.

Anyone with for instance, a plugin that adds a hook and doesn't run selftest with --no-plugins will still risk random behaviour though right? I'd like a note in the developer documentation saying doctests are not as robustly isolated as general tests.

Do you also want to uncomment the doctest we disabled the other day in this branch?

Revision history for this message
Vincent Ladeuil (vila) wrote :

> This may as well land if it fixes the problems with doctests people are
> actually having, even if it's not a complete solution.

Ok.

>
> Anyone with for instance, a plugin that adds a hook and doesn't run selftest
> with --no-plugins will still risk random behaviour though right?

Right.

> I'd like a
> note in the developer documentation saying doctests are not as robustly
> isolated as general tests.

Ok.

> Do you also want to uncomment the doctest we disabled the other day in this
> branch?

I did and used it to ensure my fix addressed the related problem.

Revision history for this message
Vincent Ladeuil (vila) wrote :

sent to pqm by email

Revision history for this message
Vincent Ladeuil (vila) wrote :

sent to pqm by email

Revision history for this message
Vincent Ladeuil (vila) wrote :

sent to pqm by email

Revision history for this message
Vincent Ladeuil (vila) wrote :

sent to pqm by email

Revision history for this message
Vincent Ladeuil (vila) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/tests/__init__.py'
2--- bzrlib/tests/__init__.py 2010-12-26 16:23:16 +0000
3+++ bzrlib/tests/__init__.py 2011-01-07 11:18:35 +0000
4@@ -123,6 +123,93 @@
5 TestSuite = TestUtil.TestSuite
6 TestLoader = TestUtil.TestLoader
7
8+# Tests should run in a clean and clearly defined environment. The goal is to
9+# keep them isolated from the running environment as mush as possible. The test
10+# framework ensures the variables defined below are set (or deleted if the
11+# value is None) before a test is run and reset to their original value after
12+# the test is run. Generally if some code depends on an environment variable,
13+# the tests should start without this variable in the environment. There are a
14+# few exceptions but you shouldn't violate this rule lightly.
15+isolated_environ = {
16+ 'BZR_HOME': None,
17+ 'HOME': None,
18+ # bzr now uses the Win32 API and doesn't rely on APPDATA, but the
19+ # tests do check our impls match APPDATA
20+ 'BZR_EDITOR': None, # test_msgeditor manipulates this variable
21+ 'VISUAL': None,
22+ 'EDITOR': None,
23+ 'BZR_EMAIL': None,
24+ 'BZREMAIL': None, # may still be present in the environment
25+ 'EMAIL': 'jrandom@example.com', # set EMAIL as bzr does not guess
26+ 'BZR_PROGRESS_BAR': None,
27+ 'BZR_LOG': None,
28+ 'BZR_PLUGIN_PATH': None,
29+ 'BZR_DISABLE_PLUGINS': None,
30+ 'BZR_PLUGINS_AT': None,
31+ 'BZR_CONCURRENCY': None,
32+ # Make sure that any text ui tests are consistent regardless of
33+ # the environment the test case is run in; you may want tests that
34+ # test other combinations. 'dumb' is a reasonable guess for tests
35+ # going to a pipe or a StringIO.
36+ 'TERM': 'dumb',
37+ 'LINES': '25',
38+ 'COLUMNS': '80',
39+ 'BZR_COLUMNS': '80',
40+ # Disable SSH Agent
41+ 'SSH_AUTH_SOCK': None,
42+ # Proxies
43+ 'http_proxy': None,
44+ 'HTTP_PROXY': None,
45+ 'https_proxy': None,
46+ 'HTTPS_PROXY': None,
47+ 'no_proxy': None,
48+ 'NO_PROXY': None,
49+ 'all_proxy': None,
50+ 'ALL_PROXY': None,
51+ # Nobody cares about ftp_proxy, FTP_PROXY AFAIK. So far at
52+ # least. If you do (care), please update this comment
53+ # -- vila 20080401
54+ 'ftp_proxy': None,
55+ 'FTP_PROXY': None,
56+ 'BZR_REMOTE_PATH': None,
57+ # Generally speaking, we don't want apport reporting on crashes in
58+ # the test envirnoment unless we're specifically testing apport,
59+ # so that it doesn't leak into the real system environment. We
60+ # use an env var so it propagates to subprocesses.
61+ 'APPORT_DISABLE': '1',
62+ }
63+
64+
65+def override_os_environ(test, env=None):
66+ """Modify os.environ keeping a copy.
67+
68+ :param test: A test instance
69+
70+ :param env: A dict containing variable definitions to be installed
71+ """
72+ if env is None:
73+ env = isolated_environ
74+ test._original_os_environ = dict([(var, value)
75+ for var, value in os.environ.iteritems()])
76+ for var, value in env.iteritems():
77+ osutils.set_or_unset_env(var, value)
78+ if var not in test._original_os_environ:
79+ # The var is new, add it with a value of None, so
80+ # restore_os_environ will delete it
81+ test._original_os_environ[var] = None
82+
83+
84+def restore_os_environ(test):
85+ """Restore os.environ to its original state.
86+
87+ :param test: A test instance previously passed to override_os_environ.
88+ """
89+ for var, value in test._original_os_environ.iteritems():
90+ # Restore the original value (or delete it if the value has been set to
91+ # None in override_os_environ).
92+ osutils.set_or_unset_env(var, value)
93+
94+
95 class ExtendedTestResult(testtools.TextTestResult):
96 """Accepts, reports and accumulates the results of running tests.
97
98@@ -794,6 +881,25 @@
99 return NullProgressView()
100
101
102+def isolated_doctest_setUp(test):
103+ override_os_environ(test)
104+
105+
106+def isolated_doctest_tearDown(test):
107+ restore_os_environ(test)
108+
109+
110+def IsolatedDocTestSuite(*args, **kwargs):
111+ """Overrides doctest.DocTestSuite to handle isolation.
112+
113+ The method is really a factory and users are expected to use it as such.
114+ """
115+
116+ kwargs['setUp'] = isolated_doctest_setUp
117+ kwargs['tearDown'] = isolated_doctest_tearDown
118+ return doctest.DocTestSuite(*args, **kwargs)
119+
120+
121 class TestCase(testtools.TestCase):
122 """Base class for bzr unit tests.
123
124@@ -1538,55 +1644,7 @@
125 return value
126
127 def _cleanEnvironment(self):
128- new_env = {
129- 'BZR_HOME': None, # Don't inherit BZR_HOME to all the tests.
130- 'HOME': os.getcwd(),
131- # bzr now uses the Win32 API and doesn't rely on APPDATA, but the
132- # tests do check our impls match APPDATA
133- 'BZR_EDITOR': None, # test_msgeditor manipulates this variable
134- 'VISUAL': None,
135- 'EDITOR': None,
136- 'BZR_EMAIL': None,
137- 'BZREMAIL': None, # may still be present in the environment
138- 'EMAIL': 'jrandom@example.com', # set EMAIL as bzr does not guess
139- 'BZR_PROGRESS_BAR': None,
140- 'BZR_LOG': None,
141- 'BZR_PLUGIN_PATH': None,
142- 'BZR_DISABLE_PLUGINS': None,
143- 'BZR_PLUGINS_AT': None,
144- 'BZR_CONCURRENCY': None,
145- # Make sure that any text ui tests are consistent regardless of
146- # the environment the test case is run in; you may want tests that
147- # test other combinations. 'dumb' is a reasonable guess for tests
148- # going to a pipe or a StringIO.
149- 'TERM': 'dumb',
150- 'LINES': '25',
151- 'COLUMNS': '80',
152- 'BZR_COLUMNS': '80',
153- # SSH Agent
154- 'SSH_AUTH_SOCK': None,
155- # Proxies
156- 'http_proxy': None,
157- 'HTTP_PROXY': None,
158- 'https_proxy': None,
159- 'HTTPS_PROXY': None,
160- 'no_proxy': None,
161- 'NO_PROXY': None,
162- 'all_proxy': None,
163- 'ALL_PROXY': None,
164- # Nobody cares about ftp_proxy, FTP_PROXY AFAIK. So far at
165- # least. If you do (care), please update this comment
166- # -- vila 20080401
167- 'ftp_proxy': None,
168- 'FTP_PROXY': None,
169- 'BZR_REMOTE_PATH': None,
170- # Generally speaking, we don't want apport reporting on crashes in
171- # the test envirnoment unless we're specifically testing apport,
172- # so that it doesn't leak into the real system environment. We
173- # use an env var so it propagates to subprocesses.
174- 'APPORT_DISABLE': '1',
175- }
176- for name, value in new_env.iteritems():
177+ for name, value in isolated_environ.iteritems():
178 self.overrideEnv(name, value)
179
180 def _captureVar(self, name, newvalue):
181@@ -3836,13 +3894,7 @@
182 return []
183 return [
184 'bzrlib',
185- # FIXME: Fixing bug #690563 revealed an isolation problem in the single
186- # doctest for branchbuilder. Uncomment this when bug #321320 is fixed
187- # to ensure the issue is addressed (note that to reproduce the bug in
188- # the doctest below, one should comment the 'email' config var in
189- # bazaar.conf (or anywhere else). This means an setup where *no* user
190- # is being set at all in the environment.
191-# 'bzrlib.branchbuilder',
192+ 'bzrlib.branchbuilder',
193 'bzrlib.decorators',
194 'bzrlib.export',
195 'bzrlib.inventory',
196@@ -3914,8 +3966,8 @@
197 try:
198 # note that this really does mean "report only" -- doctest
199 # still runs the rest of the examples
200- doc_suite = doctest.DocTestSuite(mod,
201- optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
202+ doc_suite = IsolatedDocTestSuite(
203+ mod, optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
204 except ValueError, e:
205 print '**failed to get doctest for: %s\n%s' % (mod, e)
206 raise
207
208=== modified file 'bzrlib/tests/test_selftest.py'
209--- bzrlib/tests/test_selftest.py 2010-12-26 16:23:16 +0000
210+++ bzrlib/tests/test_selftest.py 2011-01-07 11:18:35 +0000
211@@ -17,7 +17,7 @@
212 """Tests for the test framework."""
213
214 from cStringIO import StringIO
215-from doctest import ELLIPSIS
216+import doctest
217 import os
218 import signal
219 import sys
220@@ -92,7 +92,7 @@
221 "text", "plain", {"charset": "utf8"})))
222 self.assertThat(u"".join(log.iter_text()), Equals(self.get_log()))
223 self.assertThat(self.get_log(),
224- DocTestMatches(u"...a test message\n", ELLIPSIS))
225+ DocTestMatches(u"...a test message\n", doctest.ELLIPSIS))
226
227
228 class TestUnicodeFilename(tests.TestCase):
229@@ -3210,7 +3210,8 @@
230 tpr.register('bar', 'bBB.aAA.rRR')
231 self.assertEquals('bbb.aaa.rrr', tpr.get('bar'))
232 self.assertThat(self.get_log(),
233- DocTestMatches("...bar...bbb.aaa.rrr...BB.aAA.rRR", ELLIPSIS))
234+ DocTestMatches("...bar...bbb.aaa.rrr...BB.aAA.rRR",
235+ doctest.ELLIPSIS))
236
237 def test_get_unknown_prefix(self):
238 tpr = self._get_registry()
239@@ -3453,3 +3454,121 @@
240 self.fail(output.getvalue())
241 # We get our value back
242 self.assertEquals('42', os.environ.get('MYVAR'))
243+
244+
245+class TestIsolatedEnv(tests.TestCase):
246+ """Test isolating tests from os.environ.
247+
248+ Since we use tests that are already isolated from os.environ a bit of care
249+ should be taken when designing the tests to avoid bootstrap side-effects.
250+ The tests start an already clean os.environ which allow doing valid
251+ assertions about which variables are present or not and design tests around
252+ these assertions.
253+ """
254+
255+ class ScratchMonkey(tests.TestCase):
256+
257+ def test_me(self):
258+ pass
259+
260+ def test_basics(self):
261+ # Make sure we know the definition of BZR_HOME: not part of os.environ
262+ # for tests.TestCase.
263+ self.assertTrue('BZR_HOME' in tests.isolated_environ)
264+ self.assertEquals(None, tests.isolated_environ['BZR_HOME'])
265+ # Being part of isolated_environ, BZR_HOME should not appear here
266+ self.assertFalse('BZR_HOME' in os.environ)
267+ # Make sure we know the definition of LINES: part of os.environ for
268+ # tests.TestCase
269+ self.assertTrue('LINES' in tests.isolated_environ)
270+ self.assertEquals('25', tests.isolated_environ['LINES'])
271+ self.assertEquals('25', os.environ['LINES'])
272+
273+ def test_injecting_unknown_variable(self):
274+ # BZR_HOME is known to be absent from os.environ
275+ test = self.ScratchMonkey('test_me')
276+ tests.override_os_environ(test, {'BZR_HOME': 'foo'})
277+ self.assertEquals('foo', os.environ['BZR_HOME'])
278+ tests.restore_os_environ(test)
279+ self.assertFalse('BZR_HOME' in os.environ)
280+
281+ def test_injecting_known_variable(self):
282+ test = self.ScratchMonkey('test_me')
283+ # LINES is known to be present in os.environ
284+ tests.override_os_environ(test, {'LINES': '42'})
285+ self.assertEquals('42', os.environ['LINES'])
286+ tests.restore_os_environ(test)
287+ self.assertEquals('25', os.environ['LINES'])
288+
289+ def test_deleting_variable(self):
290+ test = self.ScratchMonkey('test_me')
291+ # LINES is known to be present in os.environ
292+ tests.override_os_environ(test, {'LINES': None})
293+ self.assertTrue('LINES' not in os.environ)
294+ tests.restore_os_environ(test)
295+ self.assertEquals('25', os.environ['LINES'])
296+
297+
298+class TestDocTestSuiteIsolation(tests.TestCase):
299+ """Test that `tests.DocTestSuite` isolates doc tests from os.environ.
300+
301+ Since tests.TestCase alreay provides an isolation from os.environ, we use
302+ the clean environment as a base for testing. To precisely capture the
303+ isolation provided by tests.DocTestSuite, we use doctest.DocTestSuite to
304+ compare against.
305+
306+ We want to make sure `tests.DocTestSuite` respect `tests.isolated_environ`,
307+ not `os.environ` so each test overrides it to suit its needs.
308+
309+ """
310+
311+ def get_doctest_suite_for_string(self, klass, string):
312+ class Finder(doctest.DocTestFinder):
313+
314+ def find(*args, **kwargs):
315+ test = doctest.DocTestParser().get_doctest(
316+ string, {}, 'foo', 'foo.py', 0)
317+ return [test]
318+
319+ suite = klass(test_finder=Finder())
320+ return suite
321+
322+ def run_doctest_suite_for_string(self, klass, string):
323+ suite = self.get_doctest_suite_for_string(klass, string)
324+ output = StringIO()
325+ result = tests.TextTestResult(output, 0, 1)
326+ suite.run(result)
327+ return result, output
328+
329+ def assertDocTestStringSucceds(self, klass, string):
330+ result, output = self.run_doctest_suite_for_string(klass, string)
331+ if not result.wasStrictlySuccessful():
332+ self.fail(output.getvalue())
333+
334+ def assertDocTestStringFails(self, klass, string):
335+ result, output = self.run_doctest_suite_for_string(klass, string)
336+ if result.wasStrictlySuccessful():
337+ self.fail(output.getvalue())
338+
339+ def test_injected_variable(self):
340+ self.overrideAttr(tests, 'isolated_environ', {'LINES': '42'})
341+ test = """
342+ >>> import os
343+ >>> os.environ['LINES']
344+ '42'
345+ """
346+ # doctest.DocTestSuite fails as it sees '25'
347+ self.assertDocTestStringFails(doctest.DocTestSuite, test)
348+ # tests.DocTestSuite sees '42'
349+ self.assertDocTestStringSucceds(tests.IsolatedDocTestSuite, test)
350+
351+ def test_deleted_variable(self):
352+ self.overrideAttr(tests, 'isolated_environ', {'LINES': None})
353+ test = """
354+ >>> import os
355+ >>> os.environ.get('LINES')
356+ """
357+ # doctest.DocTestSuite fails as it sees '25'
358+ self.assertDocTestStringFails(doctest.DocTestSuite, test)
359+ # tests.DocTestSuite sees None
360+ self.assertDocTestStringSucceds(tests.IsolatedDocTestSuite, test)
361
362=== modified file 'doc/developers/testing.txt'
363--- doc/developers/testing.txt 2010-12-16 17:45:14 +0000
364+++ doc/developers/testing.txt 2011-01-07 11:18:35 +0000
365@@ -329,8 +329,11 @@
366 We make selective use of doctests__. In general they should provide
367 *examples* within the API documentation which can incidentally be tested. We
368 don't try to test every important case using doctests |--| regular Python
369-tests are generally a better solution. That is, we just use doctests to
370-make our documentation testable, rather than as a way to make tests.
371+tests are generally a better solution. That is, we just use doctests to make
372+our documentation testable, rather than as a way to make tests. Be aware that
373+doctests are not as well isolated as the unit tests, if you need more
374+isolation, you're likely want to write unit tests anyway if only to get a
375+better control of the test environment.
376
377 Most of these are in ``bzrlib/doc/api``. More additions are welcome.
378
379
380=== modified file 'doc/en/release-notes/bzr-2.3.txt'
381--- doc/en/release-notes/bzr-2.3.txt 2010-12-24 16:30:36 +0000
382+++ doc/en/release-notes/bzr-2.3.txt 2011-01-07 11:18:35 +0000
383@@ -90,6 +90,13 @@
384 suite. This can include new facilities for writing tests, fixes to
385 spurious test failures and changes to the way things should be tested.
386
387+* ``bzrlib.tests`` defines ``isolated_environ`` with the definitions of all
388+ the environment variables the tests should care about. It also defines
389+ ``override_os_environ`` and ``restore_os_environ`` to properly implement
390+ isolation from ``os.environ`` for tests. ``bzrlib.tests`` now defines a
391+ ``DocTestSuite`` class using this facility for all ``bzrlib``
392+ doctests. (Vincent Ladeuil, #321320)
393+
394 * Catch exceptions related to bug #637821 during test cleanup to avoid
395 spurious failures. (Vincent Ladeuil, #686008).
396