.. 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.
.. 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,
Needs Python configured with the :option:`--with-pydebug` build option.
+ .. versionchanged:: next
+ Accept also ``module:func`` entry point format.
+
.. versionadded:: 3.13
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()
"""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
mon.free_tool_id(mon.COVERAGE_ID)
-enable()
+if __name__ == "__main__":
+ enable()
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]:
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):
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('.')
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.')
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(
--- /dev/null
+Accept a function name in :option:`-X presite <-X>` command line option and
+:envvar:`PYTHON_PRESITE` environment variable. Patch by Victor Stinner.
#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)
{
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);
+ 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_XDECREF(presite);
- Py_DECREF(presite_modname);
+ 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