├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── pytest_subinterpreter ├── __init__.py ├── faulthandler.py └── plugin.py ├── setup.py └── tests └── test_simple.py /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ["3.13.0-alpha.2"] 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v3 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | python -m pip install pytest 31 | python -m pip install . 32 | - name: Test with pytest 33 | run: | 34 | pytest 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | *.egg-info 3 | __pycache__ 4 | .venv 5 | dist/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Anthony Shaw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft pytest_subinterpreter 2 | include LICENSE 3 | include README.md 4 | include setup.py 5 | global-exclude *.py[cod] 6 | global-exclude __pycache__ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pytest-subinterpreter 2 | 3 | A Pytest extension for running the pytest session inside a [sub interpreter](https://peps.python.org/554). This extension is mainly targeted for Python extension library maintainers that want to verify their existing test suite works inside a sub interpreter. 4 | 5 | Requirements: 6 | 7 | - Python 3.13.0-alpha.2 or above 8 | - Pytest 7 or above 9 | 10 | ## Installation 11 | 12 | ```console 13 | pip install pytest-subinterpreter 14 | ``` 15 | 16 | ## Usage 17 | 18 | The extension arguments are: 19 | 20 | - `--interpreter-per` Defaults to `session` (also the only option at the moment). 21 | 22 | ```console 23 | $ pytest tests/test_simple.py 24 | ============================================================================== test session starts =============================================================================== 25 | platform darwin -- Python 3.13.0a1+, pytest-7.4.3, pluggy-1.3.0 26 | rootdir: /Users/anthonyshaw/projects/pytest-subinterpreter 27 | plugins: subinterpreter-0.0.0 28 | collected 1 item 29 | 30 | tests/test_simple.py . [100%] 31 | 32 | =============================================================================== 1 passed in 0.02s ================================================================================ 33 | 34 | ``` 35 | -------------------------------------------------------------------------------- /pytest_subinterpreter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybaloney/pytest-subinterpreter/cc11690aecc569bc7b1da8e0f4644586b2a1f1ea/pytest_subinterpreter/__init__.py -------------------------------------------------------------------------------- /pytest_subinterpreter/faulthandler.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class FaultHandler: 4 | def __init__(self): 5 | self._enabled = False 6 | 7 | def enable(self, file=sys.stderr, all_threads=True): 8 | self._enabled = True 9 | 10 | def disable(self): 11 | self._enabled = False 12 | 13 | def is_enabled(self): 14 | return self._enabled 15 | 16 | def dump_traceback_later(self, timeout, file): 17 | pass 18 | 19 | def cancel_dump_traceback_later(self): 20 | pass -------------------------------------------------------------------------------- /pytest_subinterpreter/plugin.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import _xxsubinterpreters as interpreters 3 | 4 | def pytest_addoption(parser): 5 | group = parser.getgroup("subinterpreter") 6 | group.addoption( 7 | "--interpreter-per", 8 | action="store", 9 | dest="interpreter-per", 10 | default="session", 11 | help='Interpreter scope.', 12 | ) 13 | 14 | def wrap_pytest(): 15 | # args: tuple[str], plugins: tuple[str] 16 | import sys 17 | 18 | from _pytest.main import wrap_session, _main 19 | from _pytest.config import get_config 20 | 21 | config = get_config(list(args), plugins) 22 | config.parse(list(args)) 23 | 24 | # The CPython faulthandler isn't supported, patch it with a dummy. 25 | # Pytest requires that you import the module after importing pytest things. 26 | from pytest_subinterpreter.faulthandler import FaultHandler 27 | sys.modules["faulthandler"] = FaultHandler() 28 | 29 | wrap_session(config, _main) 30 | 31 | 32 | @pytest.hookimpl(tryfirst=True) 33 | def pytest_cmdline_main(config: pytest.Config): 34 | scope = config.getoption("interpreter-per") 35 | 36 | args = tuple(config.args) 37 | plugins = tuple(config.invocation_params.plugins) if config.invocation_params.plugins else () 38 | interp = interpreters.create() 39 | 40 | if scope == "session": 41 | interpreters.run_func(interp, wrap_pytest, shared={ 42 | "args": args, 43 | "plugins": plugins 44 | }) 45 | else: 46 | raise NotImplementedError(f"Scope {scope} not implemented") 47 | 48 | interpreters.destroy(interp) 49 | return 1 # don't run main pytest script 50 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # sample ./setup.py file 2 | from setuptools import setup 3 | 4 | with open("README.md", "r") as fh: 5 | long_description = fh.read() 6 | 7 | setup( 8 | name="pytest-subinterpreter", 9 | version="0.0.2", 10 | description="Run pytest in a subinterpreter", 11 | long_description=long_description, 12 | packages=["pytest_subinterpreter"], 13 | python_requires=">=3.13", 14 | install_requires=["pytest>=7.0.0"], 15 | # the following makes a plugin available to pytest 16 | entry_points={"pytest11": ["subinterpreter = pytest_subinterpreter.plugin"]}, 17 | # custom PyPI classifier for pytest plugins 18 | classifiers=["Framework :: Pytest"], 19 | url="https://github.com/tonybaloney/pytest-subinterpreter", 20 | author="Anthony Shaw", 21 | ) -------------------------------------------------------------------------------- /tests/test_simple.py: -------------------------------------------------------------------------------- 1 | def test_foo(): 2 | assert True --------------------------------------------------------------------------------