bpo-32043: New "developer mode": "-X dev" option by vstinner · Pull Request #4413 · python/cpython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion Doc/using/cmdline.rst
13 changes: 13 additions & 0 deletions Doc/whatsnew/3.7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,19 @@ resolution on Linux and Windows.
PEP written and implemented by Victor Stinner


New Developer Mode: -X dev
--------------------------

Add a new "developer mode": ``-X dev`` command line option to enable debug
checks at runtime.

In short, ``python3 -X dev ...`` behaves as ``PYTHONMALLOC=debug python3 -W
default -X faulthandler ...``, except that the PYTHONMALLOC environment
variable is not set in practice.

See :option:`-X` ``dev`` for the details.


Other Language Changes
======================

Expand Down
69 changes: 55 additions & 14 deletions Lib/test/test_cmd_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
# Most tests are executed with environment variables ignored
# See test_cmd_line_script.py for testing of script execution

import test.support, unittest
import os
import sys
import subprocess
import sys
import tempfile
from test.support import script_helper, is_android
import unittest
from test import support
from test.support.script_helper import (
spawn_python, kill_python, assert_python_ok, assert_python_failure
spawn_python, kill_python, assert_python_ok, assert_python_failure,
interpreter_requires_environment
)

# XXX (ncoghlan): Move to script_helper and make consistent with run_python
Expand Down Expand Up @@ -132,11 +133,11 @@ def test_run_code(self):
# All good if execution is successful
assert_python_ok('-c', 'pass')

@unittest.skipUnless(test.support.FS_NONASCII, 'need support.FS_NONASCII')
@unittest.skipUnless(support.FS_NONASCII, 'need support.FS_NONASCII')
def test_non_ascii(self):
# Test handling of non-ascii data
command = ("assert(ord(%r) == %s)"
% (test.support.FS_NONASCII, ord(test.support.FS_NONASCII)))
% (support.FS_NONASCII, ord(support.FS_NONASCII)))
assert_python_ok('-c', command)

# On Windows, pass bytes to subprocess doesn't test how Python decodes the
Expand Down Expand Up @@ -179,7 +180,7 @@ def test_undecodable_code(self):
raise AssertionError("%a doesn't start with %a" % (stdout, pattern))

@unittest.skipUnless((sys.platform == 'darwin' or
is_android), 'test specific to Mac OS X and Android')
support.is_android), 'test specific to Mac OS X and Android')
def test_osx_android_utf8(self):
def check_output(text):
decoded = text.decode('utf-8', 'surrogateescape')
Expand Down Expand Up @@ -385,7 +386,7 @@ def preexec():
stderr=subprocess.PIPE,
preexec_fn=preexec)
out, err = p.communicate()
self.assertEqual(test.support.strip_python_stderr(err), b'')
self.assertEqual(support.strip_python_stderr(err), b'')
self.assertEqual(p.returncode, 42)

def test_no_stdin(self):
Expand Down Expand Up @@ -433,8 +434,8 @@ def test_del___main__(self):
# Issue #15001: PyRun_SimpleFileExFlags() did crash because it kept a
# borrowed reference to the dict of __main__ module and later modify
# the dict whereas the module was destroyed
filename = test.support.TESTFN
self.addCleanup(test.support.unlink, filename)
filename = support.TESTFN
self.addCleanup(support.unlink, filename)
with open(filename, "w") as script:
print("import sys", file=script)
print("del sys.modules['__main__']", file=script)
Expand All @@ -458,7 +459,7 @@ def test_unknown_options(self):
self.assertEqual(err.splitlines().count(b'Unknown option: -a'), 1)
self.assertEqual(b'', out)

@unittest.skipIf(script_helper.interpreter_requires_environment(),
@unittest.skipIf(interpreter_requires_environment(),
'Cannot run -I tests when PYTHON env vars are required.')
def test_isolatedmode(self):
self.verify_valid_flag('-I')
Expand All @@ -469,7 +470,7 @@ def test_isolatedmode(self):
# dummyvar to prevent extraneous -E
dummyvar="")
self.assertEqual(out.strip(), b'1 1 1')
with test.support.temp_cwd() as tmpdir:
with support.temp_cwd() as tmpdir:
fake = os.path.join(tmpdir, "uuid.py")
main = os.path.join(tmpdir, "main.py")
with open(fake, "w") as f:
Expand Down Expand Up @@ -506,6 +507,46 @@ def test_sys_flags_set(self):
with self.subTest(envar_value=value):
assert_python_ok('-c', code, **env_vars)

def run_xdev(self, code, check_exitcode=True):
env = dict(os.environ)
env.pop('PYTHONWARNINGS', None)
# Force malloc() to disable the debug hooks which are enabled
# by default for Python compiled in debug mode
env['PYTHONMALLOC'] = 'malloc'

args = (sys.executable, '-X', 'dev', '-c', code)
proc = subprocess.run(args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
env=env)
if check_exitcode:
self.assertEqual(proc.returncode, 0, proc)
return proc.stdout.rstrip()

def test_xdev(self):
out = self.run_xdev("import sys; print(sys.warnoptions)")
self.assertEqual(out, "['default']")

try:
import _testcapi
except ImportError:
pass
else:
code = "import _testcapi; _testcapi.pymem_api_misuse()"
with support.SuppressCrashReport():
out = self.run_xdev(code, check_exitcode=False)
self.assertIn("Debug memory block at address p=", out)

try:
import faulthandler
except ImportError:
pass
else:
code = "import faulthandler; print(faulthandler.is_enabled())"
out = self.run_xdev(code)
self.assertEqual(out, "True")

class IgnoreEnvironmentTest(unittest.TestCase):

def run_ignoring_vars(self, predicate, **env_vars):
Expand Down Expand Up @@ -541,8 +582,8 @@ def test_sys_flags_not_set(self):


def test_main():
test.support.run_unittest(CmdLineTest, IgnoreEnvironmentTest)
test.support.reap_children()
support.run_unittest(CmdLineTest, IgnoreEnvironmentTest)
support.reap_children()

if __name__ == "__main__":
test_main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add a new "developer mode": new "-X dev" command line option to enable debug
checks at runtime.
10 changes: 10 additions & 0 deletions Modules/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,16 @@ pymain_parse_envvars(_PyMain *pymain)
if (pymain_init_tracemalloc(pymain) < 0) {
return -1;
}
if (pymain_get_xoption(pymain, L"dev")) {
/* "python3 -X dev ..." behaves
as "PYTHONMALLOC=debug python3 -Wd -X faulthandler ..." */
core_config->allocator = "debug";
if (pymain_optlist_append(pymain, &pymain->cmdline.warning_options,
L"default") < 0) {
return -1;
}
core_config->faulthandler = 1;
}
return 0;
}

Expand Down
19 changes: 9 additions & 10 deletions Objects/obmalloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,14 @@ static struct {
void
_PyMem_GetDefaultRawAllocator(PyMemAllocatorEx *alloc_p)
{
PyMemAllocatorEx alloc = {NULL, PYRAW_FUNCS};
*alloc_p = alloc;
PyMemAllocatorEx pymem_raw = {
#ifdef Py_DEBUG
&_PyMem_Debug.raw, PYRAWDBG_FUNCS
#else
NULL, PYRAW_FUNCS
#endif
};
*alloc_p = pymem_raw;
}

int
Expand Down Expand Up @@ -274,13 +280,6 @@ _PyObject_Initialize(struct _pyobj_runtime_state *state)
void
_PyMem_Initialize(struct _pymem_runtime_state *state)
{
PyMemAllocatorEx pymem_raw = {
#ifdef Py_DEBUG
&_PyMem_Debug.raw, PYRAWDBG_FUNCS
#else
NULL, PYRAW_FUNCS
#endif
};
PyMemAllocatorEx pymem = {
#ifdef Py_DEBUG
&_PyMem_Debug.mem, PYDBG_FUNCS
Expand All @@ -296,7 +295,7 @@ _PyMem_Initialize(struct _pymem_runtime_state *state)
#endif
};

state->allocators.raw = pymem_raw;
_PyMem_GetDefaultRawAllocator(&state->allocators.raw);
state->allocators.mem = pymem;
state->allocators.obj = pyobject;

Expand Down
8 changes: 8 additions & 0 deletions Python/pystate.c