Skip to content

Commit 6ead552

Browse files
committed
Note that a number of the changes listed below were not applicable to the Py3k branch, and hence the corresponding
files are unchanged in this checkin. This checkin is also the first time the environment checking in regrtest has been forward ported to the Py3k branch. This checkin causes test_xmlrpc to fail - see issue 7165 (it's a bug in the 3.x version of xmlrpc.server) I am also getting a failure in test_telnetlib, but it isn't clear yet if that is due to these changes. Recorded merge of revisions 75400-75401,75404,75406,75414,75416,75422,75425-75428,75435,75439,75441-75444,75447-75449,75451-75453,75455-75458,75460-75469,75471-75473,75475-75477,75479-75481,75483,75486-75489 via svnmerge from svn+ssh://pythondev@svn.python.org/python/trunk ........ r75400 | r.david.murray | 2009-10-14 23:58:07 +1000 (Wed, 14 Oct 2009) | 6 lines Enhanced Issue 7058 patch, which will not be backported. Refactors the code, adds checks for stdin/out/err, cwd, and sys.path, and adds a new section in the summary for tests that modify the environment (thanks to Ezio Melotti for that suggestion). ........ r75453 | nick.coghlan | 2009-10-17 16:33:05 +1000 (Sat, 17 Oct 2009) | 1 line Correctly restore sys.stdout in test_descr ........ r75456 | nick.coghlan | 2009-10-17 17:30:40 +1000 (Sat, 17 Oct 2009) | 1 line Enhancement to the new environment checking code to print the changed items under -vv. Also includes a small tweak to allow underscores in the names of resources. ........ r75457 | nick.coghlan | 2009-10-17 17:34:27 +1000 (Sat, 17 Oct 2009) | 1 line Formatting tweak so that before and after values are vertically aligned ........ r75458 | nick.coghlan | 2009-10-17 18:21:21 +1000 (Sat, 17 Oct 2009) | 1 line Check and revert expected sys.path alterations ........ r75461 | nick.coghlan | 2009-10-18 00:40:54 +1000 (Sun, 18 Oct 2009) | 1 line Restore original sys.path when running TTK tests ........ r75462 | nick.coghlan | 2009-10-18 01:09:41 +1000 (Sun, 18 Oct 2009) | 1 line Don't invoke reload(sys) and use StringIO objects instead of real files to capture stdin and stdout when needed (ensures all sys attributes remain unmodified after test_xmlrpc runs) ........ r75463 | nick.coghlan | 2009-10-18 01:23:08 +1000 (Sun, 18 Oct 2009) | 1 line Revert changes made to environment in test_httpservers ........ r75465 | nick.coghlan | 2009-10-18 01:45:52 +1000 (Sun, 18 Oct 2009) | 1 line Move restoration of the os.environ object into the context manager where it belongs ........ r75466 | nick.coghlan | 2009-10-18 01:48:16 +1000 (Sun, 18 Oct 2009) | 1 line Also check and restore identity of sys.path, sys.argv and os.environ rather than just their values (this picked up a few more misbehaving tests) ........ r75467 | nick.coghlan | 2009-10-18 01:57:42 +1000 (Sun, 18 Oct 2009) | 1 line Avoid replacing existing modules and sys.path in import tests ........ r75468 | nick.coghlan | 2009-10-18 02:19:51 +1000 (Sun, 18 Oct 2009) | 1 line Don't replace sys.path in test_site ........ r75481 | nick.coghlan | 2009-10-18 15:38:48 +1000 (Sun, 18 Oct 2009) | 1 line Using CleanImport to revert a reload of the os module doesn't work due to function registrations in copy_reg. The perils of reloading modules even for tests... ........ r75486 | nick.coghlan | 2009-10-18 20:29:10 +1000 (Sun, 18 Oct 2009) | 1 line Silence a deprecation warning by using the appropriate replacement construct ........ r75489 | nick.coghlan | 2009-10-18 20:56:21 +1000 (Sun, 18 Oct 2009) | 1 line Restore sys.path in test_tk ........
1 parent 430fb63 commit 6ead552

10 files changed

+263
-85
lines changed

Lib/test/regrtest.py

+148-20
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
353353
bad = []
354354
skipped = []
355355
resource_denieds = []
356+
environment_changed = []
356357

357358
if findleaks:
358359
try:
@@ -429,11 +430,13 @@ def accumulate_result(test, result):
429430
test_times.append((test_time, test))
430431
if ok > 0:
431432
good.append(test)
432-
elif ok == 0:
433+
elif -2 < ok <= 0:
433434
bad.append(test)
435+
if ok == -1:
436+
environment_changed.append(test)
434437
else:
435438
skipped.append(test)
436-
if ok == -2:
439+
if ok == -3:
437440
resource_denieds.append(test)
438441

439442
if use_mp:
@@ -539,6 +542,7 @@ def work():
539542
good.sort()
540543
bad.sort()
541544
skipped.sort()
545+
environment_changed.sort()
542546

543547
if good and not quiet:
544548
if not bad and not skipped and len(good) > 1:
@@ -553,8 +557,14 @@ def work():
553557
for time, test in test_times[:10]:
554558
print("%s: %.1fs" % (test, time))
555559
if bad:
556-
print(count(len(bad), "test"), "failed:")
557-
printlist(bad)
560+
bad = sorted(set(bad) - set(environment_changed))
561+
if bad:
562+
print(count(len(bad), "test"), "failed:")
563+
printlist(bad)
564+
if environment_changed:
565+
print("{} altered the execution environment:".format(
566+
count(len(environment_changed), "test")))
567+
printlist(environment_changed)
558568
if skipped and not quiet:
559569
print(count(len(skipped), "test"), "skipped:")
560570
printlist(skipped)
@@ -657,8 +667,10 @@ def runtest(test, verbose, quiet,
657667
debug -- if true, print tracebacks for failed tests regardless of
658668
verbose setting
659669
Return:
660-
-2 test skipped because resource denied
661-
-1 test skipped for some other reason
670+
-4 KeyboardInterrupt when run under -j
671+
-3 test skipped because resource denied
672+
-2 test skipped for some other reason
673+
-1 test failed because it changed the execution environment
662674
0 test failed
663675
1 test passed
664676
"""
@@ -672,6 +684,118 @@ def runtest(test, verbose, quiet,
672684
finally:
673685
cleanup_test_droppings(test, verbose)
674686

687+
# Unit tests are supposed to leave the execution environment unchanged
688+
# once they complete. But sometimes tests have bugs, especially when
689+
# tests fail, and the changes to environment go on to mess up other
690+
# tests. This can cause issues with buildbot stability, since tests
691+
# are run in random order and so problems may appear to come and go.
692+
# There are a few things we can save and restore to mitigate this, and
693+
# the following context manager handles this task.
694+
695+
class saved_test_environment:
696+
"""Save bits of the test environment and restore them at block exit.
697+
698+
with saved_test_environment(testname, verbose, quiet):
699+
#stuff
700+
701+
Unless quiet is True, a warning is printed to stderr if any of
702+
the saved items was changed by the test. The attribute 'changed'
703+
is initially False, but is set to True if a change is detected.
704+
705+
If verbose is more than 1, the before and after state of changed
706+
items is also printed.
707+
"""
708+
709+
changed = False
710+
711+
def __init__(self, testname, verbose=0, quiet=False):
712+
self.testname = testname
713+
self.verbose = verbose
714+
self.quiet = quiet
715+
716+
# To add things to save and restore, add a name XXX to the resources list
717+
# and add corresponding get_XXX/restore_XXX functions. get_XXX should
718+
# return the value to be saved and compared against a second call to the
719+
# get function when test execution completes. restore_XXX should accept
720+
# the saved value and restore the resource using it. It will be called if
721+
# and only if a change in the value is detected.
722+
#
723+
# Note: XXX will have any '.' replaced with '_' characters when determining
724+
# the corresponding method names.
725+
726+
resources = ('sys.argv', 'cwd', 'sys.stdin', 'sys.stdout', 'sys.stderr',
727+
'os.environ', 'sys.path')
728+
729+
def get_sys_argv(self):
730+
return id(sys.argv), sys.argv, sys.argv[:]
731+
def restore_sys_argv(self, saved_argv):
732+
sys.argv = saved_argv[1]
733+
sys.argv[:] = saved_argv[2]
734+
735+
def get_cwd(self):
736+
return os.getcwd()
737+
def restore_cwd(self, saved_cwd):
738+
os.chdir(saved_cwd)
739+
740+
def get_sys_stdout(self):
741+
return sys.stdout
742+
def restore_sys_stdout(self, saved_stdout):
743+
sys.stdout = saved_stdout
744+
745+
def get_sys_stderr(self):
746+
return sys.stderr
747+
def restore_sys_stderr(self, saved_stderr):
748+
sys.stderr = saved_stderr
749+
750+
def get_sys_stdin(self):
751+
return sys.stdin
752+
def restore_sys_stdin(self, saved_stdin):
753+
sys.stdin = saved_stdin
754+
755+
def get_os_environ(self):
756+
return id(os.environ), os.environ, dict(os.environ)
757+
def restore_os_environ(self, saved_environ):
758+
os.environ = saved_environ[1]
759+
os.environ.clear()
760+
os.environ.update(saved_environ[2])
761+
762+
def get_sys_path(self):
763+
return id(sys.path), sys.path, sys.path[:]
764+
def restore_sys_path(self, saved_path):
765+
sys.path = saved_path[1]
766+
sys.path[:] = saved_path[2]
767+
768+
def resource_info(self):
769+
for name in self.resources:
770+
method_suffix = name.replace('.', '_')
771+
get_name = 'get_' + method_suffix
772+
restore_name = 'restore_' + method_suffix
773+
yield name, getattr(self, get_name), getattr(self, restore_name)
774+
775+
def __enter__(self):
776+
self.saved_values = dict((name, get()) for name, get, restore
777+
in self.resource_info())
778+
return self
779+
780+
def __exit__(self, exc_type, exc_val, exc_tb):
781+
for name, get, restore in self.resource_info():
782+
current = get()
783+
original = self.saved_values[name]
784+
# Check for changes to the resource's value
785+
if current != original:
786+
self.changed = True
787+
restore(original)
788+
if not self.quiet:
789+
print("Warning -- {} was modified by {}".format(
790+
name, self.testname),
791+
file=sys.stderr)
792+
if self.verbose > 1:
793+
print(" Before: {}\n After: {} ".format(
794+
original, current),
795+
file=sys.stderr)
796+
return False
797+
798+
675799
def runtest_inner(test, verbose, quiet,
676800
testdir=None, huntrleaks=False, debug=False):
677801
support.unload(test)
@@ -692,18 +816,20 @@ def runtest_inner(test, verbose, quiet,
692816
else:
693817
# Always import it from the test package
694818
abstest = 'test.' + test
695-
start_time = time.time()
696-
the_package = __import__(abstest, globals(), locals(), [])
697-
the_module = getattr(the_package, test)
698-
# Old tests run to completion simply as a side-effect of
699-
# being imported. For tests based on unittest or doctest,
700-
# explicitly invoke their test_main() function (if it exists).
701-
indirect_test = getattr(the_module, "test_main", None)
702-
if indirect_test is not None:
703-
indirect_test()
704-
if huntrleaks:
705-
refleak = dash_R(the_module, test, indirect_test, huntrleaks)
706-
test_time = time.time() - start_time
819+
with saved_test_environment(test, verbose, quiet) as environment:
820+
start_time = time.time()
821+
the_package = __import__(abstest, globals(), locals(), [])
822+
the_module = getattr(the_package, test)
823+
# Old tests run to completion simply as a side-effect of
824+
# being imported. For tests based on unittest or doctest,
825+
# explicitly invoke their test_main() function (if it exists).
826+
indirect_test = getattr(the_module, "test_main", None)
827+
if indirect_test is not None:
828+
indirect_test()
829+
if huntrleaks:
830+
refleak = dash_R(the_module, test, indirect_test,
831+
huntrleaks)
832+
test_time = time.time() - start_time
707833
finally:
708834
sys.stdout = save_stdout
709835
# Restore what we saved if needed, but also complain if the test
@@ -721,12 +847,12 @@ def runtest_inner(test, verbose, quiet,
721847
if not quiet:
722848
print(test, "skipped --", msg)
723849
sys.stdout.flush()
724-
return -2, test_time
850+
return -3, test_time
725851
except unittest.SkipTest as msg:
726852
if not quiet:
727853
print(test, "skipped --", msg)
728854
sys.stdout.flush()
729-
return -1, test_time
855+
return -2, test_time
730856
except KeyboardInterrupt:
731857
raise
732858
except support.TestFailed as msg:
@@ -744,6 +870,8 @@ def runtest_inner(test, verbose, quiet,
744870
else:
745871
if refleak:
746872
return 0, test_time
873+
if environment.changed:
874+
return -1, test_time
747875
return 1, test_time
748876

749877
def cleanup_test_droppings(testname, verbose):

Lib/test/support.py

+29
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,32 @@ def __exit__(self, *ignore_exc):
567567
del self._environ[k]
568568
else:
569569
self._environ[k] = v
570+
os.environ = self._environ
571+
572+
573+
class DirsOnSysPath(object):
574+
"""Context manager to temporarily add directories to sys.path.
575+
576+
This makes a copy of sys.path, appends any directories given
577+
as positional arguments, then reverts sys.path to the copied
578+
settings when the context ends.
579+
580+
Note that *all* sys.path modifications in the body of the
581+
context manager, including replacement of the object,
582+
will be reverted at the end of the block.
583+
"""
584+
585+
def __init__(self, *paths):
586+
self.original_value = sys.path[:]
587+
self.original_object = sys.path
588+
sys.path.extend(paths)
589+
590+
def __enter__(self):
591+
return self
592+
593+
def __exit__(self, *ignore_exc):
594+
sys.path = self.original_object
595+
sys.path[:] = self.original_value
570596

571597

572598
class TransientResource(object):
@@ -623,6 +649,9 @@ def captured_output(stream_name):
623649
def captured_stdout():
624650
return captured_output("stdout")
625651

652+
def captured_stdin():
653+
return captured_output("stdin")
654+
626655
def gc_collect():
627656
"""Force as many objects as possible to be collected.
628657

Lib/test/test_cmd_line.py

-10
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,6 @@ def test_q(self):
6565
self.verify_valid_flag('-Qwarnall')
6666

6767
def test_site_flag(self):
68-
if os.name == 'posix':
69-
# Workaround bug #586680 by adding the extension dir to PYTHONPATH
70-
from distutils.util import get_platform
71-
s = "./build/lib.%s-%.3s" % (get_platform(), sys.version)
72-
if hasattr(sys, 'gettotalrefcount'):
73-
s += '-pydebug'
74-
p = os.environ.get('PYTHONPATH', '')
75-
if p:
76-
p += ':'
77-
os.environ['PYTHONPATH'] = p + s
7868
self.verify_valid_flag('-S')
7969

8070
def test_usage(self):

Lib/test/test_descr.py

+3
Original file line numberDiff line numberDiff line change
@@ -4001,6 +4001,7 @@ def test_wrapper_segfault(self):
40014001
def test_file_fault(self):
40024002
# Testing sys.stdout is changed in getattr...
40034003
import sys
4004+
test_stdout = sys.stdout
40044005
class StdoutGuard:
40054006
def __getattr__(self, attr):
40064007
sys.stdout = sys.__stdout__
@@ -4010,6 +4011,8 @@ def __getattr__(self, attr):
40104011
print("Oops!")
40114012
except RuntimeError:
40124013
pass
4014+
finally:
4015+
sys.stdout = test_stdout
40134016

40144017
def test_vicious_descriptor_nonsense(self):
40154018
# Testing vicious_descriptor_nonsense...

Lib/test/test_httpservers.py

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def stop(self):
5151

5252
class BaseTestCase(unittest.TestCase):
5353
def setUp(self):
54+
os.environ = support.EnvironmentVarGuard()
5455
self.lock = threading.Lock()
5556
self.thread = TestServerThread(self, self.request_handler)
5657
self.thread.start()
@@ -59,6 +60,7 @@ def setUp(self):
5960
def tearDown(self):
6061
self.lock.release()
6162
self.thread.stop()
63+
os.environ.__exit__()
6264

6365
def request(self, uri, method='GET', body=None, headers={}):
6466
self.connection = http.client.HTTPConnection('localhost', self.PORT)

Lib/test/test_imp.py

+27-7
Original file line numberDiff line numberDiff line change
@@ -146,18 +146,38 @@ def test_issue5604(self):
146146
support.rmtree(test_package_name)
147147

148148

149-
def test_reload(self):
150-
import marshal
151-
imp.reload(marshal)
152-
import string
153-
imp.reload(string)
154-
## import sys
155-
## self.assertRaises(ImportError, reload, sys)
149+
class ReloadTests(unittest.TestCase):
150+
151+
"""Very basic tests to make sure that imp.reload() operates just like
152+
reload()."""
153+
154+
def test_source(self):
155+
# XXX (ncoghlan): It would be nice to use test_support.CleanImport
156+
# here, but that breaks because the os module registers some
157+
# handlers in copy_reg on import. Since CleanImport doesn't
158+
# revert that registration, the module is left in a broken
159+
# state after reversion. Reinitialising the module contents
160+
# and just reverting os.environ to its previous state is an OK
161+
# workaround
162+
with support.EnvironmentVarGuard():
163+
import os
164+
imp.reload(os)
165+
166+
def test_extension(self):
167+
with support.CleanImport('time'):
168+
import time
169+
imp.reload(time)
170+
171+
def test_builtin(self):
172+
with support.CleanImport('marshal'):
173+
import marshal
174+
imp.reload(marshal)
156175

157176

158177
def test_main():
159178
tests = [
160179
ImportTests,
180+
ReloadTests,
161181
]
162182
try:
163183
import _thread

0 commit comments

Comments
 (0)