Human description:
xdoctest needs a way to run an arbitrary piece of code on startup.
GPT description:
Add startup hook / preload option that runs once before doctest collection and execution
Summary
It would be useful for xdoctest to support a test-run startup hook that executes once, before doctest collection and execution.
There is already:
--global-exec GLOBAL_EXEC
Custom Python code to execute before every test
but this has different semantics: it runs before each doctest/example, after xdoctest has already started normal collection/execution machinery. Some use cases need code to run once, as early as possible in the xdoctest process.
Motivation
I ran into an import-order-sensitive native library issue involving GDAL.
A doctest like this can fail:
>>> import kwimage
>>> from osgeo import gdal
>>> from osgeo import osr
with an error like:
ImportError: /lib/x86_64-linux-gnu/libstdc++.so.6: cannot allocate memory in static TLS block
In my case the final visible exception was sometimes masked by GDAL's SWIG fallback as:
ModuleNotFoundError: No module named '_gdal'
The practical workaround is to import GDAL/OSR before other binary extension libraries that may consume static TLS:
from osgeo import gdal
from osgeo import osr
For pytest, this can be handled in conftest.py, e.g. in pytest_sessionstart. But when xdoctest is run directly:
python -m xdoctest path/to/module.py
pytest hooks are not involved.
--global-exec is close, but it is not quite the right primitive because this use case is not “setup before every doctest”; it is “startup code before doctest collection/execution, once per xdoctest process.”
Proposed feature
Add one or both of the following:
Option 1: arbitrary startup code
python -m xdoctest \
--startup-exec "from osgeo import gdal, osr" \
path/to/module.py
Semantics:
Execute Python code once at xdoctest startup, before doctest collection and execution.
Suggested help text:
--startup-exec STARTUP_EXEC
Python code to execute once at xdoctest startup, before doctest
collection and execution. Useful for import-order-sensitive libraries
and process-level initialization.
Option 2: safer module preload
python -m xdoctest \
--preload-module kwimage._test_startup \
path/to/module.py
Semantics:
Import the specified module once at xdoctest startup, before doctest collection and execution.
Suggested help text:
--preload-module MODULE
Import a module once at xdoctest startup, before doctest collection
and execution. May be specified multiple times.
This could also be exposed in config:
[tool.xdoctest]
startup_exec = [
"from osgeo import gdal, osr",
]
preload_modules = [
"kwimage._test_startup",
]
I think startup_exec and preload_modules are clearer names than preamble, because preamble could be interpreted as code prepended to each doctest, which overlaps with the existing global_exec behavior.
Why not only use --global-exec?
--global-exec is useful, but it is per-test setup. This request is for process/session startup setup.
For native library import-order issues, the timing matters. The desired behavior is:
- start xdoctest process
- run startup hook / preload modules
- collect doctests
- execute doctests, applying existing
--global-exec as usual if requested
Example workaround today
Currently the closest workaround is:
python -m xdoctest \
--global-exec "from osgeo import gdal, osr" \
path/to/module.py
or, for legacy GDAL bindings:
python -m xdoctest \
--global-exec "import kwimage._test_startup" \
path/to/module.py
where kwimage._test_startup handles both modern and legacy imports:
import importlib
def _import_first_available(names):
for name in names:
try:
return importlib.import_module(name)
except ImportError:
pass
_import_first_available(['osgeo.gdal', 'gdal'])
_import_first_available(['osgeo.osr', 'osr'])
But this is semantically different from a startup hook, and it repeats the setup before every doctest.
Open questions
- Should the feature support arbitrary code, module imports, or both?
- Should config use
startup_exec, preload_modules, or different names?
- How early can this reasonably run in xdoctest's CLI flow?
- Should this run before module discovery/imports but after xdoctest's own argument parsing?
Human description:
xdoctest needs a way to run an arbitrary piece of code on startup.
GPT description:
Add startup hook / preload option that runs once before doctest collection and execution
Summary
It would be useful for xdoctest to support a test-run startup hook that executes once, before doctest collection and execution.
There is already:
--global-exec GLOBAL_EXEC Custom Python code to execute before every testbut this has different semantics: it runs before each doctest/example, after xdoctest has already started normal collection/execution machinery. Some use cases need code to run once, as early as possible in the xdoctest process.
Motivation
I ran into an import-order-sensitive native library issue involving GDAL.
A doctest like this can fail:
with an error like:
In my case the final visible exception was sometimes masked by GDAL's SWIG fallback as:
The practical workaround is to import GDAL/OSR before other binary extension libraries that may consume static TLS:
For pytest, this can be handled in
conftest.py, e.g. inpytest_sessionstart. But when xdoctest is run directly:pytest hooks are not involved.
--global-execis close, but it is not quite the right primitive because this use case is not “setup before every doctest”; it is “startup code before doctest collection/execution, once per xdoctest process.”Proposed feature
Add one or both of the following:
Option 1: arbitrary startup code
python -m xdoctest \ --startup-exec "from osgeo import gdal, osr" \ path/to/module.pySemantics:
Suggested help text:
Option 2: safer module preload
python -m xdoctest \ --preload-module kwimage._test_startup \ path/to/module.pySemantics:
Suggested help text:
This could also be exposed in config:
I think
startup_execandpreload_modulesare clearer names thanpreamble, becausepreamblecould be interpreted as code prepended to each doctest, which overlaps with the existingglobal_execbehavior.Why not only use --global-exec?
--global-execis useful, but it is per-test setup. This request is for process/session startup setup.For native library import-order issues, the timing matters. The desired behavior is:
--global-execas usual if requestedExample workaround today
Currently the closest workaround is:
python -m xdoctest \ --global-exec "from osgeo import gdal, osr" \ path/to/module.pyor, for legacy GDAL bindings:
python -m xdoctest \ --global-exec "import kwimage._test_startup" \ path/to/module.pywhere
kwimage._test_startuphandles both modern and legacy imports:But this is semantically different from a startup hook, and it repeats the setup before every doctest.
Open questions
startup_exec,preload_modules, or different names?