Skip to content

Add startup hook #206

Description

@Erotemic

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:

  1. start xdoctest process
  2. run startup hook / preload modules
  3. collect doctests
  4. 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?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions