mirror of
https://github.com/python/cpython.git
synced 2026-04-20 02:40:59 +00:00
gh-148014: Accept a function name in -X presite option (#148015)
This commit is contained in:
parent
e65987d4c0
commit
feee573f36
10 changed files with 167 additions and 37 deletions
|
|
@ -1807,10 +1807,10 @@ PyConfig
|
|||
|
||||
.. c:member:: wchar_t* run_presite
|
||||
|
||||
``package.module`` path to module that should be imported before
|
||||
``site.py`` is run.
|
||||
``module`` or ``module:func`` entry point that should be executed before
|
||||
the :mod:`site` module is imported.
|
||||
|
||||
Set by the :option:`-X presite=package.module <-X>` command-line
|
||||
Set by the :option:`-X presite=module:func <-X>` command-line
|
||||
option and the :envvar:`PYTHON_PRESITE` environment variable.
|
||||
The command-line option takes precedence.
|
||||
|
||||
|
|
|
|||
|
|
@ -654,13 +654,17 @@ Miscellaneous options
|
|||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
* :samp:`-X presite={package.module}` specifies a module that should be
|
||||
imported before the :mod:`site` module is executed and before the
|
||||
* :samp:`-X presite={module}` or :samp:`-X presite={module:func}` specifies
|
||||
an entry point that should be executed before the :mod:`site` module is
|
||||
executed and before the
|
||||
:mod:`__main__` module exists. Therefore, the imported module isn't
|
||||
:mod:`__main__`. This can be used to execute code early during Python
|
||||
initialization. Python needs to be :ref:`built in debug mode <debug-build>`
|
||||
for this option to exist. See also :envvar:`PYTHON_PRESITE`.
|
||||
|
||||
.. versionchanged:: next
|
||||
Accept also ``module:func`` entry point format.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
* :samp:`-X gil={0,1}` forces the GIL to be disabled or enabled,
|
||||
|
|
@ -1458,4 +1462,7 @@ Debug-mode variables
|
|||
|
||||
Needs Python configured with the :option:`--with-pydebug` build option.
|
||||
|
||||
.. versionchanged:: next
|
||||
Accept also ``module:func`` entry point format.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
|
|
|||
|
|
@ -47,16 +47,21 @@ def test_sys_funcs(self):
|
|||
self.check_structseq(type(obj))
|
||||
|
||||
|
||||
try:
|
||||
unittest.main(
|
||||
module=(
|
||||
'__main__'
|
||||
if __name__ == '__main__'
|
||||
# Avoiding a circular import:
|
||||
else sys.modules['test._test_embed_structseq']
|
||||
def main():
|
||||
try:
|
||||
unittest.main(
|
||||
module=(
|
||||
'__main__'
|
||||
if __name__ == '__main__'
|
||||
# Avoiding a circular import:
|
||||
else sys.modules['test._test_embed_structseq']
|
||||
)
|
||||
)
|
||||
)
|
||||
except SystemExit as exc:
|
||||
if exc.args[0] != 0:
|
||||
raise
|
||||
print("Tests passed")
|
||||
except SystemExit as exc:
|
||||
if exc.args[0] != 0:
|
||||
raise
|
||||
print("Tests passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
"""A minimal hook for gathering line coverage of the standard library.
|
||||
|
||||
Designed to be used with -Xpresite= which means:
|
||||
* it installs itself on import
|
||||
* it's not imported as `__main__` so can't use the ifmain idiom
|
||||
Designed to be used with -Xpresite=test.cov:enable which means:
|
||||
|
||||
* it can't import anything besides `sys` to avoid tainting gathered coverage
|
||||
* filenames are not normalized
|
||||
|
||||
|
|
@ -45,4 +44,5 @@ def disable():
|
|||
mon.free_tool_id(mon.COVERAGE_ID)
|
||||
|
||||
|
||||
enable()
|
||||
if __name__ == "__main__":
|
||||
enable()
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ def create_python_cmd(self) -> list[str]:
|
|||
if '-u' not in python_opts:
|
||||
cmd.append('-u') # Unbuffered stdout and stderr
|
||||
if self.coverage:
|
||||
cmd.append("-Xpresite=test.cov")
|
||||
cmd.append("-Xpresite=test.cov:enable")
|
||||
return cmd
|
||||
|
||||
def bisect_cmd_args(self) -> list[str]:
|
||||
|
|
|
|||
|
|
@ -1397,7 +1397,7 @@ def trace_wrapper(*args, **kwargs):
|
|||
sys.settrace(original_trace)
|
||||
|
||||
coverage_wrapper = trace_wrapper
|
||||
if 'test.cov' in sys.modules: # -Xpresite=test.cov used
|
||||
if 'test.cov' in sys.modules: # -Xpresite=test.cov:enable used
|
||||
cov = sys.monitoring.COVERAGE_ID
|
||||
@functools.wraps(func)
|
||||
def coverage_wrapper(*args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -32,6 +32,17 @@ def _kill_python_and_exit_code(p):
|
|||
return data, returncode
|
||||
|
||||
|
||||
def presite_func():
|
||||
print("presite func")
|
||||
|
||||
class Namespace:
|
||||
pass
|
||||
|
||||
presite = Namespace()
|
||||
presite.attr = Namespace()
|
||||
presite.attr.func = presite_func
|
||||
|
||||
|
||||
class CmdLineTest(unittest.TestCase):
|
||||
def test_directories(self):
|
||||
assert_python_failure('.')
|
||||
|
|
@ -1266,6 +1277,17 @@ def test_invalid_thread_local_bytecode(self):
|
|||
rc, out, err = assert_python_failure(PYTHON_TLBC="2")
|
||||
self.assertIn(b"PYTHON_TLBC=N: N is missing or invalid", err)
|
||||
|
||||
@unittest.skipUnless(support.Py_DEBUG,
|
||||
'-X presite requires a Python debug build')
|
||||
def test_presite(self):
|
||||
entrypoint = "test.test_cmd_line:presite_func"
|
||||
proc = assert_python_ok("-X", f"presite={entrypoint}", "-c", "pass")
|
||||
self.assertEqual(proc.out.rstrip(), b"presite func")
|
||||
|
||||
entrypoint = "test.test_cmd_line:presite.attr.func"
|
||||
proc = assert_python_ok("-X", f"presite={entrypoint}", "-c", "pass")
|
||||
self.assertEqual(proc.out.rstrip(), b"presite func")
|
||||
|
||||
|
||||
@unittest.skipIf(interpreter_requires_environment(),
|
||||
'Cannot run -I tests when PYTHON env vars are required.')
|
||||
|
|
|
|||
|
|
@ -2051,7 +2051,7 @@ def test_no_memleak(self):
|
|||
def test_presite(self):
|
||||
cmd = [
|
||||
sys.executable,
|
||||
"-I", "-X", "presite=test._test_embed_structseq",
|
||||
"-I", "-X", "presite=test._test_embed_structseq:main",
|
||||
"-c", "print('unique-python-message')",
|
||||
]
|
||||
proc = subprocess.run(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Accept a function name in :option:`-X presite <-X>` command line option and
|
||||
:envvar:`PYTHON_PRESITE` environment variable. Patch by Victor Stinner.
|
||||
|
|
@ -1218,6 +1218,54 @@ pyinit_main_reconfigure(PyThreadState *tstate)
|
|||
|
||||
|
||||
#ifdef Py_DEBUG
|
||||
// Equivalent to the Python code:
|
||||
//
|
||||
// for part in attr.split('.'):
|
||||
// obj = getattr(obj, part)
|
||||
static PyObject*
|
||||
presite_resolve_name(PyObject *obj, PyObject *attr)
|
||||
{
|
||||
obj = Py_NewRef(obj);
|
||||
attr = Py_NewRef(attr);
|
||||
PyObject *res;
|
||||
|
||||
while (1) {
|
||||
Py_ssize_t len = PyUnicode_GET_LENGTH(attr);
|
||||
Py_ssize_t pos = PyUnicode_FindChar(attr, '.', 0, len, 1);
|
||||
if (pos < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
PyObject *name = PyUnicode_Substring(attr, 0, pos);
|
||||
if (name == NULL) {
|
||||
goto error;
|
||||
}
|
||||
res = PyObject_GetAttr(obj, name);
|
||||
Py_DECREF(name);
|
||||
if (res == NULL) {
|
||||
goto error;
|
||||
}
|
||||
Py_SETREF(obj, res);
|
||||
|
||||
PyObject *suffix = PyUnicode_Substring(attr, pos + 1, len);
|
||||
if (suffix == NULL) {
|
||||
goto error;
|
||||
}
|
||||
Py_SETREF(attr, suffix);
|
||||
}
|
||||
|
||||
res = PyObject_GetAttr(obj, attr);
|
||||
Py_DECREF(obj);
|
||||
Py_DECREF(attr);
|
||||
return res;
|
||||
|
||||
error:
|
||||
Py_DECREF(obj);
|
||||
Py_DECREF(attr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
run_presite(PyThreadState *tstate)
|
||||
{
|
||||
|
|
@ -1228,22 +1276,68 @@ run_presite(PyThreadState *tstate)
|
|||
return;
|
||||
}
|
||||
|
||||
PyObject *presite_modname = PyUnicode_FromWideChar(
|
||||
config->run_presite,
|
||||
wcslen(config->run_presite)
|
||||
);
|
||||
if (presite_modname == NULL) {
|
||||
fprintf(stderr, "Could not convert pre-site module name to unicode\n");
|
||||
PyObject *presite = PyUnicode_FromWideChar(config->run_presite, -1);
|
||||
if (presite == NULL) {
|
||||
fprintf(stderr, "Could not convert pre-site command to Unicode\n");
|
||||
_PyErr_Print(tstate);
|
||||
return;
|
||||
}
|
||||
|
||||
// Accept "mod_name" and "mod_name:func_name" entry point syntax
|
||||
Py_ssize_t len = PyUnicode_GET_LENGTH(presite);
|
||||
Py_ssize_t pos = PyUnicode_FindChar(presite, ':', 0, len, 1);
|
||||
PyObject *mod_name = NULL;
|
||||
PyObject *func_name = NULL;
|
||||
PyObject *module = NULL;
|
||||
if (pos > 0) {
|
||||
mod_name = PyUnicode_Substring(presite, 0, pos);
|
||||
if (mod_name == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
func_name = PyUnicode_Substring(presite, pos + 1, len);
|
||||
if (func_name == NULL) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else {
|
||||
PyObject *presite = PyImport_Import(presite_modname);
|
||||
if (presite == NULL) {
|
||||
fprintf(stderr, "pre-site import failed:\n");
|
||||
_PyErr_Print(tstate);
|
||||
}
|
||||
Py_XDECREF(presite);
|
||||
Py_DECREF(presite_modname);
|
||||
mod_name = Py_NewRef(presite);
|
||||
}
|
||||
|
||||
// mod_name can contain dots (ex: "math.integer")
|
||||
module = PyImport_Import(mod_name);
|
||||
if (module == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (func_name != NULL) {
|
||||
PyObject *func = presite_resolve_name(module, func_name);
|
||||
if (func == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
PyObject *res = PyObject_CallNoArgs(func);
|
||||
Py_DECREF(func);
|
||||
if (res == NULL) {
|
||||
goto error;
|
||||
}
|
||||
Py_DECREF(res);
|
||||
}
|
||||
|
||||
Py_DECREF(presite);
|
||||
Py_DECREF(mod_name);
|
||||
Py_XDECREF(func_name);
|
||||
Py_DECREF(module);
|
||||
return;
|
||||
|
||||
error:
|
||||
fprintf(stderr, "pre-site failed:\n");
|
||||
_PyErr_Print(tstate);
|
||||
|
||||
Py_DECREF(presite);
|
||||
Py_XDECREF(mod_name);
|
||||
Py_XDECREF(func_name);
|
||||
Py_XDECREF(module);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue