blob: 3724f27b23b24fcd141e8081e2deee00efd2a5af [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
Victor Stinner3f746852015-09-29 13:47:15 +0200249 tracer = trace.Trace(trace=False, count=True)
Victor Stinner3844fe52015-09-26 10:38:01 +0200250
251 test_times = []
252 support.verbose = ns.verbose # Tell tests to be moderately quiet
253 support.use_resources = ns.use_resources
254 save_modules = sys.modules.keys()
255
256 def accumulate_result(test, result):
257 ok, test_time = result
258 test_times.append((test_time, test))
259 if ok == PASSED:
260 good.append(test)
261 elif ok == FAILED:
262 bad.append(test)
263 elif ok == ENV_CHANGED:
264 environment_changed.append(test)
265 elif ok == SKIPPED:
266 skipped.append(test)
267 elif ok == RESOURCE_DENIED:
268 skipped.append(test)
269 resource_denieds.append(test)
270
271 if ns.forever:
272 def test_forever(tests=list(selected)):
273 while True:
274 for test in tests:
275 yield test
276 if bad:
277 return
278 tests = test_forever()
279 test_count = ''
280 test_count_width = 3
281 else:
282 tests = iter(selected)
283 test_count = '/{}'.format(len(selected))
284 test_count_width = len(test_count) - 1
285
286 if ns.use_mp:
287 try:
288 from threading import Thread
289 except ImportError:
290 print("Multiprocess option requires thread support")
291 sys.exit(2)
292 from queue import Queue
293 debug_output_pat = re.compile(r"\[\d+ refs, \d+ blocks\]$")
294 output = Queue()
295 pending = MultiprocessTests(tests)
296 def work():
297 # A worker thread.
298 try:
299 while True:
300 try:
301 test = next(pending)
302 except StopIteration:
303 output.put((None, None, None, None))
304 return
305 retcode, stdout, stderr = run_test_in_subprocess(test, ns)
306 # Strip last refcount output line if it exists, since it
307 # comes from the shutdown of the interpreter in the subcommand.
308 stderr = debug_output_pat.sub("", stderr)
309 stdout, _, result = stdout.strip().rpartition("\n")
310 if retcode != 0:
311 result = (CHILD_ERROR, "Exit code %s" % retcode)
312 output.put((test, stdout.rstrip(), stderr.rstrip(), result))
313 return
314 if not result:
315 output.put((None, None, None, None))
316 return
317 result = json.loads(result)
318 output.put((test, stdout.rstrip(), stderr.rstrip(), result))
319 except BaseException:
320 output.put((None, None, None, None))
321 raise
322 workers = [Thread(target=work) for i in range(ns.use_mp)]
323 for worker in workers:
324 worker.start()
325 finished = 0
326 test_index = 1
327 try:
328 while finished < ns.use_mp:
329 test, stdout, stderr, result = output.get()
330 if test is None:
331 finished += 1
332 continue
333 accumulate_result(test, result)
334 if not ns.quiet:
335 fmt = "[{1:{0}}{2}/{3}] {4}" if bad else "[{1:{0}}{2}] {4}"
336 print(fmt.format(
337 test_count_width, test_index, test_count,
338 len(bad), test))
339 if stdout:
340 print(stdout)
341 if stderr:
342 print(stderr, file=sys.stderr)
343 sys.stdout.flush()
344 sys.stderr.flush()
345 if result[0] == INTERRUPTED:
346 raise KeyboardInterrupt
347 if result[0] == CHILD_ERROR:
348 raise Exception("Child error on {}: {}".format(test, result[1]))
349 test_index += 1
350 except KeyboardInterrupt:
351 interrupted = True
352 pending.interrupted = True
353 for worker in workers:
354 worker.join()
355 else:
356 for test_index, test in enumerate(tests, 1):
357 if not ns.quiet:
358 fmt = "[{1:{0}}{2}/{3}] {4}" if bad else "[{1:{0}}{2}] {4}"
359 print(fmt.format(
360 test_count_width, test_index, test_count, len(bad), test))
361 sys.stdout.flush()
362 if ns.trace:
363 # If we're tracing code coverage, then we don't exit with status
364 # if on a false return value from main.
365 tracer.runctx('runtest(test, ns.verbose, ns.quiet, timeout=ns.timeout)',
366 globals=globals(), locals=vars())
367 else:
368 try:
369 result = runtest(test, ns.verbose, ns.quiet,
370 ns.huntrleaks,
371 output_on_failure=ns.verbose3,
372 timeout=ns.timeout, failfast=ns.failfast,
373 match_tests=ns.match_tests)
374 accumulate_result(test, result)
375 except KeyboardInterrupt:
376 interrupted = True
377 break
378 if ns.findleaks:
379 gc.collect()
380 if gc.garbage:
381 print("Warning: test created", len(gc.garbage), end=' ')
382 print("uncollectable object(s).")
383 # move the uncollectable objects somewhere so we don't see
384 # them again
385 found_garbage.extend(gc.garbage)
386 del gc.garbage[:]
387 # Unload the newly imported modules (best effort finalization)
388 for module in sys.modules.keys():
389 if module not in save_modules and module.startswith("test."):
390 support.unload(module)
391
392 if interrupted:
393 # print a newline after ^C
394 print()
395 print("Test suite interrupted by signal SIGINT.")
396 omitted = set(selected) - set(good) - set(bad) - set(skipped)
397 print(count(len(omitted), "test"), "omitted:")
398 printlist(omitted)
399 if good and not ns.quiet:
400 if not bad and not skipped and not interrupted and len(good) > 1:
401 print("All", end=' ')
402 print(count(len(good), "test"), "OK.")
403 if ns.print_slow:
404 test_times.sort(reverse=True)
405 print("10 slowest tests:")
406 for time, test in test_times[:10]:
407 print("%s: %.1fs" % (test, time))
408 if bad:
409 print(count(len(bad), "test"), "failed:")
410 printlist(bad)
411 if environment_changed:
412 print("{} altered the execution environment:".format(
413 count(len(environment_changed), "test")))
414 printlist(environment_changed)
415 if skipped and not ns.quiet:
416 print(count(len(skipped), "test"), "skipped:")
417 printlist(skipped)
418
419 if ns.verbose2 and bad:
420 print("Re-running failed tests in verbose mode")
421 for test in bad[:]:
422 print("Re-running test %r in verbose mode" % test)
423 sys.stdout.flush()
424 try:
425 ns.verbose = True
426 ok = runtest(test, True, ns.quiet, ns.huntrleaks,
427 timeout=ns.timeout)
428 except KeyboardInterrupt:
429 # print a newline separate from the ^C
430 print()
431 break
432 else:
433 if ok[0] in {PASSED, ENV_CHANGED, SKIPPED, RESOURCE_DENIED}:
434 bad.remove(test)
435 else:
436 if bad:
437 print(count(len(bad), 'test'), "failed again:")
438 printlist(bad)
439
440 if ns.single:
441 if next_single_test:
442 with open(filename, 'w') as fp:
443 fp.write(next_single_test + '\n')
444 else:
445 os.unlink(filename)
446
447 if ns.trace:
448 r = tracer.results()
449 r.write_results(show_missing=True, summary=True, coverdir=ns.coverdir)
450
451 if ns.runleaks:
452 os.system("leaks %d" % os.getpid())
453
454 sys.exit(len(bad) > 0 or interrupted)
455
456
457# We do not use a generator so multiple threads can call next().
458class MultiprocessTests(object):
459
460 """A thread-safe iterator over tests for multiprocess mode."""
461
462 def __init__(self, tests):
463 self.interrupted = False
464 self.lock = threading.Lock()
465 self.tests = tests
466
467 def __iter__(self):
468 return self
469
470 def __next__(self):
471 with self.lock:
472 if self.interrupted:
473 raise StopIteration('tests interrupted')
474 return next(self.tests)
475
476
477def replace_stdout():
478 """Set stdout encoder error handler to backslashreplace (as stderr error
479 handler) to avoid UnicodeEncodeError when printing a traceback"""
480 import atexit
481
482 stdout = sys.stdout
483 sys.stdout = open(stdout.fileno(), 'w',
484 encoding=stdout.encoding,
485 errors="backslashreplace",
486 closefd=False,
487 newline='\n')
488
489 def restore_stdout():
490 sys.stdout.close()
491 sys.stdout = stdout
492 atexit.register(restore_stdout)
493
494
495def removepy(names):
496 if not names:
497 return
498 for idx, name in enumerate(names):
499 basename, ext = os.path.splitext(name)
500 if ext == '.py':
501 names[idx] = basename
502
503
504def count(n, word):
505 if n == 1:
506 return "%d %s" % (n, word)
507 else:
508 return "%d %ss" % (n, word)
509
510
511def printlist(x, width=70, indent=4):
512 """Print the elements of iterable x to stdout.
513
514 Optional arg width (default 70) is the maximum line length.
515 Optional arg indent (default 4) is the number of blanks with which to
516 begin each line.
517 """
518
519 from textwrap import fill
520 blanks = ' ' * indent
521 # Print the sorted list: 'x' may be a '--random' list or a set()
522 print(fill(' '.join(str(elt) for elt in sorted(x)), width,
523 initial_indent=blanks, subsequent_indent=blanks))
524
525
526def main_in_temp_cwd():
527 """Run main() in a temporary working directory."""
528 if sysconfig.is_python_build():
529 try:
530 os.mkdir(TEMPDIR)
531 except FileExistsError:
532 pass
533
534 # Define a writable temp dir that will be used as cwd while running
535 # the tests. The name of the dir includes the pid to allow parallel
536 # testing (see the -j option).
537 test_cwd = 'test_python_{}'.format(os.getpid())
538 test_cwd = os.path.join(TEMPDIR, test_cwd)
539
540 # Run the tests in a context manager that temporarily changes the CWD to a
541 # temporary and writable directory. If it's not possible to create or
542 # change the CWD, the original CWD will be used. The original CWD is
543 # available from support.SAVEDCWD.
544 with support.temp_cwd(test_cwd, quiet=True):
545 main()