mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	Issue #18948: improve SuppressCoreFiles to include Windows crash popup suppression, and use it in more tests.
Patch by Valerie Lambert and Zachary Ware.
This commit is contained in:
		
							parent
							
								
									3ebbb04af2
								
							
						
					
					
						commit
						77e904e6a6
					
				
					 8 changed files with 87 additions and 94 deletions
				
			
		|  | @ -442,13 +442,6 @@ The :mod:`test.support` module defines the following functions: | ||||||
|    A decorator for running tests that require support for symbolic links. |    A decorator for running tests that require support for symbolic links. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| .. function:: suppress_crash_popup() |  | ||||||
| 
 |  | ||||||
|    A context manager that disables Windows Error Reporting dialogs using |  | ||||||
|    `SetErrorMode <http://msdn.microsoft.com/en-us/library/windows/desktop/ms680621%28v=vs.85%29.aspx>`_. |  | ||||||
|    On other platforms it's a no-op. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| .. decorator:: anticipate_failure(condition) | .. decorator:: anticipate_failure(condition) | ||||||
| 
 | 
 | ||||||
|    A decorator to conditionally mark tests with |    A decorator to conditionally mark tests with | ||||||
|  | @ -593,6 +586,21 @@ The :mod:`test.support` module defines the following classes: | ||||||
|    Temporarily unset the environment variable ``envvar``. |    Temporarily unset the environment variable ``envvar``. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | .. class:: SuppressCrashReport() | ||||||
|  | 
 | ||||||
|  |    A context manager used to try to prevent crash dialog popups on tests that | ||||||
|  |    are expected to crash a subprocess. | ||||||
|  | 
 | ||||||
|  |    On Windows, it disables Windows Error Reporting dialogs using | ||||||
|  |    `SetErrorMode <http://msdn.microsoft.com/en-us/library/windows/desktop/ms680621.aspx>`_. | ||||||
|  | 
 | ||||||
|  |    On UNIX, :func:`resource.setrlimit` is used to set | ||||||
|  |    :attr:`resource.RLIMIT_CORE`'s soft limit to 0 to prevent coredump file | ||||||
|  |    creation. | ||||||
|  | 
 | ||||||
|  |    On both platforms, the old value is restored by :meth:`__exit__`. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| .. class:: WarningsRecorder() | .. class:: WarningsRecorder() | ||||||
| 
 | 
 | ||||||
|    Class used to record warnings for unit tests. See documentation of |    Class used to record warnings for unit tests. See documentation of | ||||||
|  |  | ||||||
|  | @ -81,8 +81,7 @@ | ||||||
|     "TestHandler", "Matcher", "can_symlink", "skip_unless_symlink", |     "TestHandler", "Matcher", "can_symlink", "skip_unless_symlink", | ||||||
|     "skip_unless_xattr", "import_fresh_module", "requires_zlib", |     "skip_unless_xattr", "import_fresh_module", "requires_zlib", | ||||||
|     "PIPE_MAX_SIZE", "failfast", "anticipate_failure", "run_with_tz", |     "PIPE_MAX_SIZE", "failfast", "anticipate_failure", "run_with_tz", | ||||||
|     "requires_gzip", "requires_bz2", "requires_lzma", "suppress_crash_popup", |     "requires_gzip", "requires_bz2", "requires_lzma", "SuppressCrashReport" | ||||||
|     "SuppressCoreFiles", |  | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
| class Error(Exception): | class Error(Exception): | ||||||
|  | @ -2013,27 +2012,67 @@ def skip_unless_xattr(test): | ||||||
|     return test if ok else unittest.skip(msg)(test) |     return test if ok else unittest.skip(msg)(test) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if sys.platform.startswith('win'): | class SuppressCrashReport: | ||||||
|     @contextlib.contextmanager |     """Try to prevent a crash report from popping up. | ||||||
|     def suppress_crash_popup(): | 
 | ||||||
|         """Disable Windows Error Reporting dialogs using SetErrorMode.""" |     On Windows, don't display the Windows Error Reporting dialog.  On UNIX, | ||||||
|         # see http://msdn.microsoft.com/en-us/library/windows/desktop/ms680621%28v=vs.85%29.aspx |     disable the creation of coredump file. | ||||||
|  |     """ | ||||||
|  |     old_value = None | ||||||
|  | 
 | ||||||
|  |     def __enter__(self): | ||||||
|  |         """On Windows, disable Windows Error Reporting dialogs using | ||||||
|  |         SetErrorMode. | ||||||
|  | 
 | ||||||
|  |         On UNIX, try to save the previous core file size limit, then set | ||||||
|  |         soft limit to 0. | ||||||
|  |         """ | ||||||
|  |         if sys.platform.startswith('win'): | ||||||
|  |             # see http://msdn.microsoft.com/en-us/library/windows/desktop/ms680621.aspx | ||||||
|             # GetErrorMode is not available on Windows XP and Windows Server 2003, |             # GetErrorMode is not available on Windows XP and Windows Server 2003, | ||||||
|             # but SetErrorMode returns the previous value, so we can use that |             # but SetErrorMode returns the previous value, so we can use that | ||||||
|             import ctypes |             import ctypes | ||||||
|         k32 = ctypes.windll.kernel32 |             self._k32 = ctypes.windll.kernel32 | ||||||
|             SEM_NOGPFAULTERRORBOX = 0x02 |             SEM_NOGPFAULTERRORBOX = 0x02 | ||||||
|         old_error_mode = k32.SetErrorMode(SEM_NOGPFAULTERRORBOX) |             self.old_value = self._k32.SetErrorMode(SEM_NOGPFAULTERRORBOX) | ||||||
|         k32.SetErrorMode(old_error_mode | SEM_NOGPFAULTERRORBOX) |             self._k32.SetErrorMode(self.old_value | SEM_NOGPFAULTERRORBOX) | ||||||
|  |         else: | ||||||
|  |             if resource is not None: | ||||||
|                 try: |                 try: | ||||||
|             yield |                     self.old_value = resource.getrlimit(resource.RLIMIT_CORE) | ||||||
|         finally: |                     resource.setrlimit(resource.RLIMIT_CORE, | ||||||
|             k32.SetErrorMode(old_error_mode) |                                        (0, self.old_value[1])) | ||||||
| else: |                 except (ValueError, OSError): | ||||||
|     # this is a no-op for other platforms |                     pass | ||||||
|     @contextlib.contextmanager |             if sys.platform == 'darwin': | ||||||
|     def suppress_crash_popup(): |                 # Check if the 'Crash Reporter' on OSX was configured | ||||||
|         yield |                 # in 'Developer' mode and warn that it will get triggered | ||||||
|  |                 # when it is. | ||||||
|  |                 # | ||||||
|  |                 # This assumes that this context manager is used in tests | ||||||
|  |                 # that might trigger the next manager. | ||||||
|  |                 value = subprocess.Popen(['/usr/bin/defaults', 'read', | ||||||
|  |                         'com.apple.CrashReporter', 'DialogType'], | ||||||
|  |                         stdout=subprocess.PIPE).communicate()[0] | ||||||
|  |                 if value.strip() == b'developer': | ||||||
|  |                     print("this test triggers the Crash Reporter, " | ||||||
|  |                           "that is intentional", end='', flush=True) | ||||||
|  | 
 | ||||||
|  |         return self | ||||||
|  | 
 | ||||||
|  |     def __exit__(self, *ignore_exc): | ||||||
|  |         """Restore Windows ErrorMode or core file behavior to initial value.""" | ||||||
|  |         if self.old_value is None: | ||||||
|  |             return | ||||||
|  | 
 | ||||||
|  |         if sys.platform.startswith('win'): | ||||||
|  |             self._k32.SetErrorMode(self.old_value) | ||||||
|  |         else: | ||||||
|  |             if resource is not None: | ||||||
|  |                 try: | ||||||
|  |                     resource.setrlimit(resource.RLIMIT_CORE, self.old_value) | ||||||
|  |                 except (ValueError, OSError): | ||||||
|  |                     pass | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def patch(test_instance, object_to_patch, attr_name, new_value): | def patch(test_instance, object_to_patch, attr_name, new_value): | ||||||
|  | @ -2068,42 +2107,3 @@ def cleanup(): | ||||||
| 
 | 
 | ||||||
|     # actually override the attribute |     # actually override the attribute | ||||||
|     setattr(object_to_patch, attr_name, new_value) |     setattr(object_to_patch, attr_name, new_value) | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class SuppressCoreFiles: |  | ||||||
| 
 |  | ||||||
|     """Try to prevent core files from being created.""" |  | ||||||
|     old_limit = None |  | ||||||
| 
 |  | ||||||
|     def __enter__(self): |  | ||||||
|         """Try to save previous ulimit, then set the soft limit to 0.""" |  | ||||||
|         if resource is not None: |  | ||||||
|             try: |  | ||||||
|                 self.old_limit = resource.getrlimit(resource.RLIMIT_CORE) |  | ||||||
|                 resource.setrlimit(resource.RLIMIT_CORE, (0, self.old_limit[1])) |  | ||||||
|             except (ValueError, OSError): |  | ||||||
|                 pass |  | ||||||
|         if sys.platform == 'darwin': |  | ||||||
|             # Check if the 'Crash Reporter' on OSX was configured |  | ||||||
|             # in 'Developer' mode and warn that it will get triggered |  | ||||||
|             # when it is. |  | ||||||
|             # |  | ||||||
|             # This assumes that this context manager is used in tests |  | ||||||
|             # that might trigger the next manager. |  | ||||||
|             value = subprocess.Popen(['/usr/bin/defaults', 'read', |  | ||||||
|                     'com.apple.CrashReporter', 'DialogType'], |  | ||||||
|                     stdout=subprocess.PIPE).communicate()[0] |  | ||||||
|             if value.strip() == b'developer': |  | ||||||
|                 print("this test triggers the Crash Reporter, " |  | ||||||
|                       "that is intentional", end='') |  | ||||||
|                 sys.stdout.flush() |  | ||||||
| 
 |  | ||||||
|     def __exit__(self, *ignore_exc): |  | ||||||
|         """Return core file behavior to default.""" |  | ||||||
|         if self.old_limit is None: |  | ||||||
|             return |  | ||||||
|         if resource is not None: |  | ||||||
|             try: |  | ||||||
|                 resource.setrlimit(resource.RLIMIT_CORE, self.old_limit) |  | ||||||
|             except (ValueError, OSError): |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|  | @ -44,7 +44,7 @@ def test_instancemethod(self): | ||||||
| 
 | 
 | ||||||
|     @unittest.skipUnless(threading, 'Threading required for this test.') |     @unittest.skipUnless(threading, 'Threading required for this test.') | ||||||
|     def test_no_FatalError_infinite_loop(self): |     def test_no_FatalError_infinite_loop(self): | ||||||
|         with support.suppress_crash_popup(): |         with support.SuppressCrashReport(): | ||||||
|             p = subprocess.Popen([sys.executable, "-c", |             p = subprocess.Popen([sys.executable, "-c", | ||||||
|                                   'import _testcapi;' |                                   'import _testcapi;' | ||||||
|                                   '_testcapi.crash_no_current_thread()'], |                                   '_testcapi.crash_no_current_thread()'], | ||||||
|  |  | ||||||
|  | @ -19,18 +19,6 @@ | ||||||
| 
 | 
 | ||||||
| TIMEOUT = 0.5 | TIMEOUT = 0.5 | ||||||
| 
 | 
 | ||||||
| try: |  | ||||||
|     from resource import setrlimit, RLIMIT_CORE, error as resource_error |  | ||||||
| except ImportError: |  | ||||||
|     prepare_subprocess = None |  | ||||||
| else: |  | ||||||
|     def prepare_subprocess(): |  | ||||||
|         # don't create core file |  | ||||||
|         try: |  | ||||||
|             setrlimit(RLIMIT_CORE, (0, 0)) |  | ||||||
|         except (ValueError, resource_error): |  | ||||||
|             pass |  | ||||||
| 
 |  | ||||||
| def expected_traceback(lineno1, lineno2, header, min_count=1): | def expected_traceback(lineno1, lineno2, header, min_count=1): | ||||||
|     regex = header |     regex = header | ||||||
|     regex += '  File "<string>", line %s in func\n' % lineno1 |     regex += '  File "<string>", line %s in func\n' % lineno1 | ||||||
|  | @ -59,10 +47,8 @@ def get_output(self, code, filename=None): | ||||||
|         build, and replace "Current thread 0x00007f8d8fbd9700" by "Current |         build, and replace "Current thread 0x00007f8d8fbd9700" by "Current | ||||||
|         thread XXX". |         thread XXX". | ||||||
|         """ |         """ | ||||||
|         options = {} |         with support.SuppressCrashReport(): | ||||||
|         if prepare_subprocess: |             process = script_helper.spawn_python('-c', code) | ||||||
|             options['preexec_fn'] = prepare_subprocess |  | ||||||
|         process = script_helper.spawn_python('-c', code, **options) |  | ||||||
|         stdout, stderr = process.communicate() |         stdout, stderr = process.communicate() | ||||||
|         exitcode = process.wait() |         exitcode = process.wait() | ||||||
|         output = support.strip_python_stderr(stdout) |         output = support.strip_python_stderr(stdout) | ||||||
|  | @ -101,7 +87,6 @@ def check_fatal_error(self, code, line_number, name_regex, | ||||||
|             header=re.escape(header)) |             header=re.escape(header)) | ||||||
|         if other_regex: |         if other_regex: | ||||||
|             regex += '|' + other_regex |             regex += '|' + other_regex | ||||||
|         with support.suppress_crash_popup(): |  | ||||||
|         output, exitcode = self.get_output(code, filename) |         output, exitcode = self.get_output(code, filename) | ||||||
|         output = '\n'.join(output) |         output = '\n'.join(output) | ||||||
|         self.assertRegex(output, regex) |         self.assertRegex(output, regex) | ||||||
|  | @ -232,7 +217,6 @@ def test_disable(self): | ||||||
| faulthandler._sigsegv() | faulthandler._sigsegv() | ||||||
| """.strip() | """.strip() | ||||||
|         not_expected = 'Fatal Python error' |         not_expected = 'Fatal Python error' | ||||||
|         with support.suppress_crash_popup(): |  | ||||||
|         stderr, exitcode = self.get_output(code) |         stderr, exitcode = self.get_output(code) | ||||||
|         stder = '\n'.join(stderr) |         stder = '\n'.join(stderr) | ||||||
|         self.assertTrue(not_expected not in stderr, |         self.assertTrue(not_expected not in stderr, | ||||||
|  |  | ||||||
|  | @ -1231,7 +1231,7 @@ def test_start_new_session(self): | ||||||
| 
 | 
 | ||||||
|     def test_run_abort(self): |     def test_run_abort(self): | ||||||
|         # returncode handles signal termination |         # returncode handles signal termination | ||||||
|         with support.SuppressCoreFiles(): |         with support.SuppressCrashReport(): | ||||||
|             p = subprocess.Popen([sys.executable, "-c", |             p = subprocess.Popen([sys.executable, "-c", | ||||||
|                                   'import os; os.abort()']) |                                   'import os; os.abort()']) | ||||||
|             p.wait() |             p.wait() | ||||||
|  |  | ||||||
|  | @ -306,7 +306,7 @@ def test_swap_item(self): | ||||||
|     # args_from_interpreter_flags |     # args_from_interpreter_flags | ||||||
|     # can_symlink |     # can_symlink | ||||||
|     # skip_unless_symlink |     # skip_unless_symlink | ||||||
|     # SuppressCoreFiles |     # SuppressCrashReport | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_main(): | def test_main(): | ||||||
|  |  | ||||||
|  | @ -250,7 +250,7 @@ def f(): | ||||||
| 
 | 
 | ||||||
|             sys.setrecursionlimit(%d) |             sys.setrecursionlimit(%d) | ||||||
|             f()""") |             f()""") | ||||||
|         with test.support.suppress_crash_popup(): |         with test.support.SuppressCrashReport(): | ||||||
|             for i in (50, 1000): |             for i in (50, 1000): | ||||||
|                 sub = subprocess.Popen([sys.executable, '-c', code % i], |                 sub = subprocess.Popen([sys.executable, '-c', code % i], | ||||||
|                     stderr=subprocess.PIPE) |                     stderr=subprocess.PIPE) | ||||||
|  |  | ||||||
|  | @ -839,6 +839,7 @@ def f(): | ||||||
| 
 | 
 | ||||||
|             _testcapi.run_in_subinterp(%r) |             _testcapi.run_in_subinterp(%r) | ||||||
|             """ % (subinterp_code,) |             """ % (subinterp_code,) | ||||||
|  |         with test.support.SuppressCrashReport(): | ||||||
|             rc, out, err = assert_python_failure("-c", script) |             rc, out, err = assert_python_failure("-c", script) | ||||||
|         self.assertIn("Fatal Python error: Py_EndInterpreter: " |         self.assertIn("Fatal Python error: Py_EndInterpreter: " | ||||||
|                       "not the last thread", err.decode()) |                       "not the last thread", err.decode()) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Antoine Pitrou
						Antoine Pitrou