blob: 5d34cffd4921aac7ae6d4ee6f1e5de6424112381 [file] [log] [blame]
Victor Stinner3844fe52015-09-26 10:38:01 +02001import faulthandler
2import json
3import os
4import re
5import sys
6import tempfile
7import sysconfig
8import signal
9import random
10import platform
11import traceback
12import unittest
13from test.libregrtest.runtest import (
14 findtests, runtest, run_test_in_subprocess,
15 STDTESTS, NOTTESTS,
16 PASSED, FAILED, ENV_CHANGED, SKIPPED,
17 RESOURCE_DENIED, INTERRUPTED, CHILD_ERROR)
18from test.libregrtest.refleak import warm_caches
19from test.libregrtest.cmdline import _parse_args
20from test import support
21try:
22 import threading
23except ImportError:
24 threading = None
25
26
27# Some times __path__ and __file__ are not absolute (e.g. while running from
28# Lib/) and, if we change the CWD to run the tests in a temporary dir, some
29# imports might fail. This affects only the modules imported before os.chdir().
30# These modules are searched first in sys.path[0] (so '' -- the CWD) and if
31# they are found in the CWD their __file__ and __path__ will be relative (this
32# happens before the chdir). All the modules imported after the chdir, are
33# not found in the CWD, and since the other paths in sys.path[1:] are absolute
34# (site.py absolutize them), the __file__ and __path__ will be absolute too.
35# Therefore it is necessary to absolutize manually the __file__ and __path__ of
36# the packages to prevent later imports to fail when the CWD is different.
37for module in sys.modules.values():
38 if hasattr(module, '__path__'):
39 module.__path__ = [os.path.abspath(path) for path in module.__path__]
40 if hasattr(module, '__file__'):
41 module.__file__ = os.path.abspath(module.__file__)
42
43
44# MacOSX (a.k.a. Darwin) has a default stack size that is too small
45# for deeply recursive regular expressions. We see this as crashes in
46# the Python test suite when running test_re.py and test_sre.py. The
47# fix is to set the stack limit to 2048.
48# This approach may also be useful for other Unixy platforms that
49# suffer from small default stack limits.
50if sys.platform == 'darwin':
51 try:
52 import resource
53 except ImportError:
54 pass
55 else:
56 soft, hard = resource.getrlimit(resource.RLIMIT_STACK)
57 newsoft = min(hard, max(soft, 1024*2048))
58 resource.setrlimit(resource.RLIMIT_STACK, (newsoft, hard))
59
60
61# When tests are run from the Python build directory, it is best practice
62# to keep the test files in a subfolder. This eases the cleanup of leftover
63# files using the "make distclean" command.
64if sysconfig.is_python_build():
65 TEMPDIR = os.path.join(sysconfig.get_config_var('srcdir'), 'build')
66else:
67 TEMPDIR = tempfile.gettempdir()
68TEMPDIR = os.path.abspath(TEMPDIR)
69
70
71def main(tests=None, **kwargs):
72 """Execute a test suite.
73
74 This also parses command-line options and modifies its behavior
75 accordingly.
76
77 tests -- a list of strings containing test names (optional)
78 testdir -- the directory in which to look for tests (optional)
79
80 Users other than the Python test suite will certainly want to
81 specify testdir; if it's omitted, the directory containing the
82 Python test suite is searched for.
83
84 If the tests argument is omitted, the tests listed on the
85 command-line will be used. If that's empty, too, then all *.py
86 files beginning with test_ will be used.
87
88 The other default arguments (verbose, quiet, exclude,
89 single, randomize, findleaks, use_resources, trace, coverdir,
90 print_slow, and random_seed) allow programmers calling main()
91 directly to set the values that would normally be set by flags
92 on the command line.
93 """
94 # Display the Python traceback on fatal errors (e.g. segfault)
95 faulthandler.enable(all_threads=True)
96
97 # Display the Python traceback on SIGALRM or SIGUSR1 signal
98 signals = []
99 if hasattr(signal, 'SIGALRM'):
100 signals.append(signal.SIGALRM)
101 if hasattr(signal, 'SIGUSR1'):
102 signals.append(signal.SIGUSR1)
103 for signum in signals:
104 faulthandler.register(signum, chain=True)
105
106 replace_stdout()
107
108 support.record_original_stdout(sys.stdout)
109
110 ns = _parse_args(sys.argv[1:], **kwargs)
111
112 if ns.huntrleaks:
113 # Avoid false positives due to various caches
114 # filling slowly with random data:
115 warm_caches()
116 if ns.memlimit is not None:
117 support.set_memlimit(ns.memlimit)
118 if ns.threshold is not None:
119 import gc
120 gc.set_threshold(ns.threshold)
121 if ns.nowindows:
122 import msvcrt
123 msvcrt.SetErrorMode(msvcrt.SEM_FAILCRITICALERRORS|
124 msvcrt.SEM_NOALIGNMENTFAULTEXCEPT|
125 msvcrt.SEM_NOGPFAULTERRORBOX|
126 msvcrt.SEM_NOOPENFILEERRORBOX)
127 try:
128 msvcrt.CrtSetReportMode
129 except AttributeError:
130 # release build
131 pass
132 else:
133 for m in [msvcrt.CRT_WARN, msvcrt.CRT_ERROR, msvcrt.CRT_ASSERT]:
134 msvcrt.CrtSetReportMode(m, msvcrt.CRTDBG_MODE_FILE)
135 msvcrt.CrtSetReportFile(m, msvcrt.CRTDBG_FILE_STDERR)
136 if ns.wait:
137 input("Press any key to continue...")
138
139 if ns.slaveargs is not None:
140 args, kwargs = json.loads(ns.slaveargs)
141 if kwargs.get('huntrleaks'):
142 unittest.BaseTestSuite._cleanup = False
143 try:
144 result = runtest(*args, **kwargs)
145 except KeyboardInterrupt:
146 result = INTERRUPTED, ''
147 except BaseException as e:
148 traceback.print_exc()
149 result = CHILD_ERROR, str(e)
150 sys.stdout.flush()
151 print() # Force a newline (just in case)
152 print(json.dumps(result))
153 sys.exit(0)
154
155 good = []
156 bad = []
157 skipped = []
158 resource_denieds = []
159 environment_changed = []
160 interrupted = False
161
162 if ns.findleaks:
163 try:
164 import gc
165 except ImportError:
166 print('No GC available, disabling findleaks.')
167 ns.findleaks = False
168 else:
169 # Uncomment the line below to report garbage that is not
170 # freeable by reference counting alone. By default only
171 # garbage that is not collectable by the GC is reported.
172 #gc.set_debug(gc.DEBUG_SAVEALL)
173 found_garbage = []
174
175 if ns.huntrleaks:
176 unittest.BaseTestSuite._cleanup = False
177
178 if ns.single:
179 filename = os.path.join(TEMPDIR, 'pynexttest')
180 try:
181 with open(filename, 'r') as fp:
182 next_test = fp.read().strip()
183 tests = [next_test]
184 except OSError:
185 pass
186
187 if ns.fromfile:
188 tests = []
189 with open(os.path.join(support.SAVEDCWD, ns.fromfile)) as fp:
190 count_pat = re.compile(r'\[\s*\d+/\s*\d+\]')
191 for line in fp:
192 line = count_pat.sub('', line)
193 guts = line.split() # assuming no test has whitespace in its name
194 if guts and not guts[0].startswith('#'):
195 tests.extend(guts)
196
197 # Strip .py extensions.
198 removepy(ns.args)
199 removepy(tests)
200
201 stdtests = STDTESTS[:]
202 nottests = NOTTESTS.copy()
203 if ns.exclude:
204 for arg in ns.args:
205 if arg in stdtests:
206 stdtests.remove(arg)
207 nottests.add(arg)
208 ns.args = []
209
210 # For a partial run, we do not need to clutter the output.
211 if ns.verbose or ns.header or not (ns.quiet or ns.single or tests or ns.args):
212 # Print basic platform information
213 print("==", platform.python_implementation(), *sys.version.split())
214 print("== ", platform.platform(aliased=True),
215 "%s-endian" % sys.byteorder)
216 print("== ", "hash algorithm:", sys.hash_info.algorithm,
217 "64bit" if sys.maxsize > 2**32 else "32bit")
218 print("== ", os.getcwd())
219 print("Testing with flags:", sys.flags)
220
221 # if testdir is set, then we are not running the python tests suite, so
222 # don't add default tests to be executed or skipped (pass empty values)
223 if ns.testdir:
224 alltests = findtests(ns.testdir, list(), set())
225 else:
226 alltests = findtests(ns.testdir, stdtests, nottests)
227
228 selected = tests or ns.args or alltests
229 if ns.single:
230 selected = selected[:1]
231 try:
232 next_single_test = alltests[alltests.index(selected[0])+1]
233 except IndexError:
234 next_single_test = None
235 # Remove all the selected tests that precede start if it's set.
236 if ns.start:
237 try:
238 del selected[:selected.index(ns.start)]
239 except ValueError:
240 print("Couldn't find starting test (%s), using all tests" % ns.start)
241 if ns.randomize:
242 if ns.random_seed is None:
243 ns.random_seed = random.randrange(10000000)
244 random.seed(ns.random_seed)
245 print("Using random seed", ns.random_seed)
246 random.shuffle(selected)
247 if ns.trace:
248 import trace, tempfile
249 tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,
250 tempfile.gettempdir()],
251 trace=False, count=True)
252
253 test_times = []
254 support.verbose = ns.verbose # Tell tests to be moderately quiet
255 support.use_resources = ns.use_resources
256 save_modules = sys.modules.keys()
257
258 def accumulate_result(test, result):
259 ok, test_time = result
260 test_times.append((test_time, test))
261 if ok == PASSED:
262 good.append(test)
263 elif ok == FAILED:
264 bad.append(test)
265 elif ok == ENV_CHANGED:
266 environment_changed.append(test)
267 elif ok == SKIPPED:
268 skipped.append(test)
269 elif ok == RESOURCE_DENIED:
270 skipped.append(test)
271 resource_denieds.append(test)
272
273 if ns.forever:
274 def test_forever(tests=list(selected)):
275 while True:
276 for test in tests:
277 yield test
278 if bad:
279 return
280 tests = test_forever()
281 test_count = ''
282 test_count_width = 3
283 else:
284 tests = iter(selected)
285 test_count = '/{}'.format(len(selected))
286 test_count_width = len(test_count) - 1
287
288 if ns.use_mp:
289 try:
290 from threading import Thread
291 except ImportError:
292 print("Multiprocess option requires thread support")
293 sys.exit(2)
294 from queue import Queue
295 debug_output_pat = re.compile(r"\[\d+ refs, \d+ blocks\]$")
296 output = Queue()
297 pending = MultiprocessTests(tests)
298 def work():
299 # A worker thread.
300 try:
301 while True:
302 try:
303 test = next(pending)
304 except StopIteration:
305 output.put((None, None, None, None))
306 return
307 retcode, stdout, stderr = run_test_in_subprocess(test, ns)
308 # Strip last refcount output line if it exists, since it
309 # comes from the shutdown of the interpreter in the subcommand.
310 stderr = debug_output_pat.sub("", stderr)
311 stdout, _, result = stdout.strip().rpartition("\n")
312 if retcode != 0:
313 result = (CHILD_ERROR, "Exit code %s" % retcode)
314 output.put((test, stdout.rstrip(), stderr.rstrip(), result))
315 return
316 if not result:
317 output.put((None, None, None, None))
318 return
319 result = json.loads(result)
320 output.put((test, stdout.rstrip(), stderr.rstrip(), result))
321 except BaseException:
322 output.put((None, None, None, None))
323 raise
324 workers = [Thread(target=work) for i in range(ns.use_mp)]
325 for worker in workers:
326 worker.start()
327 finished = 0
328 test_index = 1
329 try:
330 while finished < ns.use_mp:
331 test, stdout, stderr, result = output.get()
332 if test is None:
333 finished += 1
334 continue
335 accumulate_result(test, result)
336 if not ns.quiet:
337 fmt = "[{1:{0}}{2}/{3}] {4}" if bad else "[{1:{0}}{2}] {4}"
338 print(fmt.format(
339 test_count_width, test_index, test_count,
340 len(bad), test))
341 if stdout:
342 print(stdout)
343 if stderr:
344 print(stderr, file=sys.stderr)
345 sys.stdout.flush()
346 sys.stderr.flush()
347 if result[0] == INTERRUPTED:
348 raise KeyboardInterrupt
349 if result[0] == CHILD_ERROR:
350 raise Exception("Child error on {}: {}".format(test, result[1]))
351 test_index += 1
352 except KeyboardInterrupt:
353 interrupted = True
354 pending.interrupted = True
355 for worker in workers:
356 worker.join()
357 else:
358 for test_index, test in enumerate(tests, 1):
359 if not ns.quiet:
360 fmt = "[{1:{0}}{2}/{3}] {4}" if bad else "[{1:{0}}{2}] {4}"
361 print(fmt.format(
362 test_count_width, test_index, test_count, len(bad), test))
363 sys.stdout.flush()
364 if ns.trace:
365 # If we're tracing code coverage, then we don't exit with status
366 # if on a false return value from main.
367 tracer.runctx('runtest(test, ns.verbose, ns.quiet, timeout=ns.timeout)',
368 globals=globals(), locals=vars())
369 else:
370 try:
371 result = runtest(test, ns.verbose, ns.quiet,
372 ns.huntrleaks,
373 output_on_failure=ns.verbose3,
374 timeout=ns.timeout, failfast=ns.failfast,
375 match_tests=ns.match_tests)
376 accumulate_result(test, result)
377 except KeyboardInterrupt:
378 interrupted = True
379 break
380 if ns.findleaks:
381 gc.collect()
382 if gc.garbage:
383 print("Warning: test created", len(gc.garbage), end=' ')
384 print("uncollectable object(s).")
385 # move the uncollectable objects somewhere so we don't see
386 # them again
387 found_garbage.extend(gc.garbage)
388 del gc.garbage[:]
389 # Unload the newly imported modules (best effort finalization)
390 for module in sys.modules.keys():
391 if module not in save_modules and module.startswith("test."):
392 support.unload(module)
393
394 if interrupted:
395 # print a newline after ^C
396 print()
397 print("Test suite interrupted by signal SIGINT.")
398 omitted = set(selected) - set(good) - set(bad) - set(skipped)
399 print(count(len(omitted), "test"), "omitted:")
400 printlist(omitted)
401 if good and not ns.quiet:
402 if not bad and not skipped and not interrupted and len(good) > 1:
403 print("All", end=' ')
404 print(count(len(good), "test"), "OK.")
405 if ns.print_slow:
406 test_times.sort(reverse=True)
407 print("10 slowest tests:")
408 for time, test in test_times[:10]:
409 print("%s: %.1fs" % (test, time))
410 if bad:
411 print(count(len(bad), "test"), "failed:")
412 printlist(bad)
413 if environment_changed:
414 print("{} altered the execution environment:".format(
415 count(len(environment_changed), "test")))
416 printlist(environment_changed)
417 if skipped and not ns.quiet:
418 print(count(len(skipped), "test"), "skipped:")
419 printlist(skipped)
420
421 if ns.verbose2 and bad:
422 print("Re-running failed tests in verbose mode")
423 for test in bad[:]:
424 print("Re-running test %r in verbose mode" % test)
425 sys.stdout.flush()
426 try:
427 ns.verbose = True
428 ok = runtest(test, True, ns.quiet, ns.huntrleaks,
429 timeout=ns.timeout)
430 except KeyboardInterrupt:
431 # print a newline separate from the ^C
432 print()
433 break
434 else:
435 if ok[0] in {PASSED, ENV_CHANGED, SKIPPED, RESOURCE_DENIED}:
436 bad.remove(test)
437 else:
438 if bad:
439 print(count(len(bad), 'test'), "failed again:")
440 printlist(bad)
441
442 if ns.single:
443 if next_single_test:
444 with open(filename, 'w') as fp:
445 fp.write(next_single_test + '\n')
446 else:
447 os.unlink(filename)
448
449 if ns.trace:
450 r = tracer.results()
451 r.write_results(show_missing=True, summary=True, coverdir=ns.coverdir)
452
453 if ns.runleaks:
454 os.system("leaks %d" % os.getpid())
455
456 sys.exit(len(bad) > 0 or interrupted)
457
458
459# We do not use a generator so multiple threads can call next().
460class MultiprocessTests(object):
461
462 """A thread-safe iterator over tests for multiprocess mode."""
463
464 def __init__(self, tests):
465 self.interrupted = False
466 self.lock = threading.Lock()
467 self.tests = tests
468
469 def __iter__(self):
470 return self
471
472 def __next__(self):
473 with self.lock:
474 if self.interrupted:
475 raise StopIteration('tests interrupted')
476 return next(self.tests)
477
478
479def replace_stdout():
480 """Set stdout encoder error handler to backslashreplace (as stderr error
481 handler) to avoid UnicodeEncodeError when printing a traceback"""
482 import atexit
483
484 stdout = sys.stdout
485 sys.stdout = open(stdout.fileno(), 'w',
486 encoding=stdout.encoding,
487 errors="backslashreplace",
488 closefd=False,
489 newline='\n')
490
491 def restore_stdout():
492 sys.stdout.close()
493 sys.stdout = stdout
494 atexit.register(restore_stdout)
495
496
497def removepy(names):
498 if not names:
499 return
500 for idx, name in enumerate(names):
501 basename, ext = os.path.splitext(name)
502 if ext == '.py':
503 names[idx] = basename
504
505
506def count(n, word):
507 if n == 1:
508 return "%d %s" % (n, word)
509 else:
510 return "%d %ss" % (n, word)
511
512
513def printlist(x, width=70, indent=4):
514 """Print the elements of iterable x to stdout.
515
516 Optional arg width (default 70) is the maximum line length.
517 Optional arg indent (default 4) is the number of blanks with which to
518 begin each line.
519 """
520
521 from textwrap import fill
522 blanks = ' ' * indent
523 # Print the sorted list: 'x' may be a '--random' list or a set()
524 print(fill(' '.join(str(elt) for elt in sorted(x)), width,
525 initial_indent=blanks, subsequent_indent=blanks))
526
527
528def main_in_temp_cwd():
529 """Run main() in a temporary working directory."""
530 if sysconfig.is_python_build():
531 try:
532 os.mkdir(TEMPDIR)
533 except FileExistsError:
534 pass
535
536 # Define a writable temp dir that will be used as cwd while running
537 # the tests. The name of the dir includes the pid to allow parallel
538 # testing (see the -j option).
539 test_cwd = 'test_python_{}'.format(os.getpid())
540 test_cwd = os.path.join(TEMPDIR, test_cwd)
541
542 # Run the tests in a context manager that temporarily changes the CWD to a
543 # temporary and writable directory. If it's not possible to create or
544 # change the CWD, the original CWD will be used. The original CWD is
545 # available from support.SAVEDCWD.
546 with support.temp_cwd(test_cwd, quiet=True):
547 main()