diff options
-rw-r--r-- | .coveragerc | 7 | ||||
-rw-r--r-- | .editorconfig | 15 | ||||
-rw-r--r-- | .flake8 | 9 | ||||
-rw-r--r-- | .github/FUNDING.yml | 2 | ||||
-rw-r--r-- | .github/dependabot.yml | 8 | ||||
-rw-r--r-- | .github/workflows/main.yml | 42 | ||||
-rw-r--r-- | .pre-commit-config.yaml | 10 | ||||
-rw-r--r-- | .readthedocs.yml | 6 | ||||
-rw-r--r-- | CHANGES.rst | 230 | ||||
-rw-r--r-- | LICENSE | 19 | ||||
-rw-r--r-- | README.rst | 140 | ||||
-rw-r--r-- | docs/conf.py | 33 | ||||
-rw-r--r-- | docs/history.rst | 8 | ||||
-rw-r--r-- | docs/index.rst | 17 | ||||
-rw-r--r-- | mypy.ini | 2 | ||||
-rw-r--r-- | ptr.py | 222 | ||||
-rw-r--r-- | pyproject.toml | 20 | ||||
-rw-r--r-- | pytest.ini | 7 | ||||
-rw-r--r-- | setup.cfg | 63 | ||||
-rw-r--r-- | setup.py | 21 | ||||
-rw-r--r-- | tests/test_ptr.py | 173 | ||||
-rw-r--r-- | tox.ini | 40 |
22 files changed, 1091 insertions, 3 deletions
diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..6a34e66 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,7 @@ +[run] +omit = + # leading `*/` for pytest-dev/pytest-cov#456 + */.tox/* + +[report] +show_missing = True diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6385b57 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +indent_style = tab +indent_size = 4 +insert_final_newline = true +end_of_line = lf + +[*.py] +indent_style = space + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 88 + +# jaraco/skeleton#34 +max-complexity = 10 + +extend-ignore = + # Black creates whitespace before colon + E203 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 230b556..3b4273a 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -tidelift: pypi/PROJECT +tidelift: pypi/pytest-runner diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..89ff339 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + allow: + - dependency-type: "all" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..6a8ff00 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,42 @@ +name: tests + +on: [push, pull_request] + +jobs: + test: + strategy: + matrix: + python: [3.6, 3.8, 3.9] + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install tox + run: | + python -m pip install tox + - name: Run tests + run: tox + + release: + needs: test + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install tox + run: | + python -m pip install tox + - name: Release + run: tox -e release + env: + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c15ab0c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: +- repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black + +- repo: https://github.com/asottile/blacken-docs + rev: v1.9.1 + hooks: + - id: blacken-docs diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..cc69854 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,6 @@ +version: 2 +python: + install: + - path: . + extra_requirements: + - docs diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 0000000..2d225f7 --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,230 @@ +v5.3.1 +====== + +* Refreshed package metadata. + +v5.3.0 +====== + +* Require Python 3.6 or later. +* Refreshed package metadata. + +5.2 +=== + +* #50: This project is deprecated. + +5.1 +=== + +* #49: Surgically restore support for older setuptools versions. + +5.0 +=== + +* #42: Prefer pyproject.toml +* Refresh package metadata. +* This release now intentionally introduces the changes + unintionally brought about in 4.5 and 4.3, where the + adoption of declarative config adds a new requirement + on setuptools 30.4 or later. On systems running older + setuptools, installation of pytest-runner via + ``easy_install`` (or ``setup_requires``), will result + in a ``DistributionNotFound`` exception. + + All projects should pin to ``pytest-runner < 5`` + or upgrade the environment to ``setuptools >= 30.4`` + (prior to invoking setup.py). + +4.5.1 +===== + +* #48: Revert changes from 4.5 - restoring project to the + state at 4.4. + +4.5 +=== + +(Pulled from PyPI due to #43 and #48) + +* Packaging (skeleton) refresh, including adoption of + `black <https://pypi.org/project/black>`_ for style. + +4.4 +=== + +* #43: Detect condition where declarative config will cause + errors and emit a UserWarning with guidance on necessary + actions. + +4.3.1 +===== + +* #43: Re-release of 4.2 to supersede the 4.3 release which + proved to be backward-incompatible in that it requires + setuptools 30.4 or possibly later (to install). In the future, a + backward-incompatible release will re-release these changes. + For projects including pytest-runner, particularly as + ``setup_requires``, if support for older setuptools is required, + please pin to ``pytest-runner < 5``. + +4.3 +=== + +(Pulled from PyPI due to #43) + +* #42: Update project metadata, including pyproject.toml declaration. + +4.2 +=== + +* #40: Remove declared dependency and instead assert it at + run time. + +4.1 +=== + +* #40: Declare dependency on Setuptools in package metadata. + +4.0 +=== + +* Drop support for Setuptools before Setuptools 27.3.0. + +3.0.1 +===== + +* #38: Fixed AttributeError when running with ``--dry-run``. + ``PyTest.run()`` no longer stores nor returns the result code. + Based on the commit message for `840ff4c < + https://github.com/pytest-dev/pytest-runner/commit/840ff4c2bf6c752d9770f0dd8d64a841060cf9bc>`_, + nothing has ever relied on that value. + +3.0 +=== + +* Dropped support for Python 2.6 and 3.1. + +2.12.2 +====== + +* #33: Packaging refresh. + +2.12.1 +====== + +* #32: Fix support for ``dependency_links``. + +2.12 +==== + +* #30: Rework support for ``--allow-hosts`` and + ``--index-url``, removing dependence on + ``setuptools.Distribution``'s private member. + Additionally corrects logic in marker evaluation + along with unit tests! + +2.11.1 +====== + +* #28: Fix logic in marker evaluation. + +2.11 +==== + +* #27: Improved wording in the README around configuration + for the distutils command and pytest proper. + +2.10.1 +====== + +* #21: Avoid mutating dictionary keys during iteration. + +2.10 +==== + +* #20: Leverage technique in `setuptools 794 + <https://github.com/pypa/setuptools/issues/794>`_ + to populate PYTHONPATH during test runs such that + Python subprocesses will have a dependency context + comparable to the test runner. + +2.9 +=== + +* Added Trove Classifier indicating this package is part + of the pytest framework. + +2.8 +=== + +* #16: Added a license file, required for membership to + pytest-dev. +* Releases are now made automatically by pushing a + tagged release that passes tests on Python 3.5. + +2.7 +=== + +* Moved hosting to Github. + +2.6 +=== + +* Add support for un-named, environment-specific extras. + +2.5.1 +===== + +* Restore Python 2.6 compatibility. + +2.5 +=== + +* Moved hosting to `pytest-dev + <https://bitbucket.org/pytest-dev/pytest-runner>`_. + +2.4 +=== + +* Added `documentation <https://pythonhosted.org/pytest-runner>`_. +* Use setuptools_scm for version management and file discovery. +* Updated internal packaging technique. README is now included + in the package metadata. + +2.3 +=== + +* Use hgdistver for version management and file discovery. + +2.2 +=== + +* Honor ``.eggs`` directory for transient downloads as introduced in Setuptools + 7.0. + +2.1 +=== + +* The preferred invocation is now the 'pytest' command. + +2.0 +=== + +* Removed support for the alternate usage. The recommended usage (as a + distutils command) is now the only supported usage. +* Removed support for the --junitxml parameter to the ptr command. Clients + should pass the same parameter (and all other py.test arguments) to py.test + via the --addopts parameter. + +1.1 +=== + +* Added support for --addopts to pass any arguments through to py.test. +* Deprecated support for --junitxml. Use --addopts instead. --junitxml will be + removed in 2.0. + +1.0 +=== + +Initial implementation. @@ -0,0 +1,19 @@ +Copyright Jason R. Coombs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. @@ -1,5 +1,141 @@ -.. image:: https://tidelift.com/badges/package/pypi/PROJECT - :target: https://tidelift.com/subscription/pkg/pypi-PROJECT?utm_source=pypi-PROJECT&utm_medium=readme +.. image:: https://img.shields.io/pypi/v/pytest-runner.svg + :target: `PyPI link`_ + +.. image:: https://img.shields.io/pypi/pyversions/pytest-runner.svg + :target: `PyPI link`_ + +.. _PyPI link: https://pypi.org/project/pytest-runner + +.. image:: https://github.com/pytest-dev/pytest-runner/workflows/tests/badge.svg + :target: https://github.com/pytest-dev/pytest-runner/actions?query=workflow%3A%22tests%22 + :alt: tests + +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: Black + +.. .. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest +.. :target: https://skeleton.readthedocs.io/en/latest/?badge=latest + +.. image:: https://img.shields.io/badge/skeleton-2021-informational + :target: https://blog.jaraco.com/skeleton + +.. image:: https://tidelift.com/badges/package/pypi/pytest-runner + :target: https://tidelift.com/subscription/pkg/pypi-pytest-runner?utm_source=pypi-pytest-runner&utm_medium=readme + +Setup scripts can use pytest-runner to add setup.py test support for pytest +runner. + +Deprecation Notice +================== + +pytest-runner depends on deprecated features of setuptools and relies on features that break security +mechanisms in pip. For example 'setup_requires' and 'tests_require' bypass ``pip --require-hashes``. +See also `pypa/setuptools#1684 <https://github.com/pypa/setuptools/issues/1684>`_. + +It is recommended that you: + +- Remove ``'pytest-runner'`` from your ``setup_requires``, preferably removing the ``setup_requires`` option. +- Remove ``'pytest'`` and any other testing requirements from ``tests_require``, preferably removing the ``tests_requires`` option. +- Select a tool to bootstrap and then run tests such as tox. + +Usage +===== + +- Add 'pytest-runner' to your 'setup_requires'. Pin to '>=2.0,<3dev' (or + similar) to avoid pulling in incompatible versions. +- Include 'pytest' and any other testing requirements to 'tests_require'. +- Invoke tests with ``setup.py pytest``. +- Pass ``--index-url`` to have test requirements downloaded from an alternate + index URL (unnecessary if specified for easy_install in setup.cfg). +- Pass additional py.test command-line options using ``--addopts``. +- Set permanent options for the ``python setup.py pytest`` command (like ``index-url``) + in the ``[pytest]`` section of ``setup.cfg``. +- Set permanent options for the ``py.test`` run (like ``addopts`` or ``pep8ignore``) in the ``[pytest]`` + section of ``pytest.ini`` or ``tox.ini`` or put them in the ``[tool:pytest]`` + section of ``setup.cfg``. See `pytest issue 567 + <https://github.com/pytest-dev/pytest/issues/567>`_. +- Optionally, set ``test=pytest`` in the ``[aliases]`` section of ``setup.cfg`` + to cause ``python setup.py test`` to invoke pytest. + +Example +======= + +The most simple usage looks like this in setup.py:: + + setup( + setup_requires=[ + 'pytest-runner', + ], + tests_require=[ + 'pytest', + ], + ) + +Additional dependencies require to run the tests (e.g. mock or pytest +plugins) may be added to tests_require and will be downloaded and +required by the session before invoking pytest. + +Follow `this search on github +<https://github.com/search?utf8=%E2%9C%93&q=filename%3Asetup.py+pytest-runner&type=Code&ref=searchresults>`_ +for examples of real-world usage. + +Standalone Example +================== + +This technique is deprecated - if you have standalone scripts +you wish to invoke with dependencies, `use pip-run +<https://pypi.org/project/pip-run>`_. + +Although ``pytest-runner`` is typically used to add pytest test +runner support to maintained packages, ``pytest-runner`` may +also be used to create standalone tests. Consider `this example +failure <https://gist.github.com/jaraco/d979a558bc0bf2194c23>`_, +reported in `jsonpickle #117 +<https://github.com/jsonpickle/jsonpickle/issues/117>`_ +or `this MongoDB test +<https://gist.github.com/jaraco/0b9e482f5c0a1300dc9a>`_ +demonstrating a technique that works even when dependencies +are required in the test. + +Either example file may be cloned or downloaded and simply run on +any system with Python and Setuptools. It will download the +specified dependencies and run the tests. Afterward, the the +cloned directory can be removed and with it all trace of +invoking the test. No other dependencies are needed and no +system configuration is altered. + +Then, anyone trying to replicate the failure can do so easily +and with all the power of pytest (rewritten assertions, +rich comparisons, interactive debugging, extensibility through +plugins, etc). + +As a result, the communication barrier for describing and +replicating failures is made almost trivially low. + +Considerations +============== + +Conditional Requirement +----------------------- + +Because it uses Setuptools setup_requires, pytest-runner will install itself +on every invocation of setup.py. In some cases, this causes delays for +invocations of setup.py that will never invoke pytest-runner. To help avoid +this contingency, consider requiring pytest-runner only when pytest +is invoked:: + + needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv) + pytest_runner = ['pytest-runner'] if needs_pytest else [] + + # ... + + setup( + #... + setup_requires=[ + #... (other setup requirements) + ] + pytest_runner, + ) For Enterprise ============== diff --git a/docs/conf.py b/docs/conf.py index dbf962d..2ac23dd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,3 +1,36 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +extensions = ['sphinx.ext.autodoc', 'jaraco.packaging.sphinx', 'rst.linker'] + +master_doc = "index" + +link_files = { + '../CHANGES.rst': dict( + using=dict(GH='https://github.com'), + replace=[ + dict( + pattern=r'(Issue #|\B#)(?P<issue>\d+)', + url='{package_url}/issues/{issue}', + ), + dict( + pattern=r'(?m:^((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n)', + with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', + ), + dict( + pattern=r'PEP[- ](?P<pep_number>\d+)', + url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/', + ), + dict( + pattern=r'Setuptools #(?P<setuptools_issue>\d+)', + url='https://github.com/pypa/setuptools' '/issues/{setuptools_issue}/', + ), + ], + ) +} + +# Be strict about any broken references: +nitpicky = True # Custom sidebar templates, maps document names to template names. html_theme = 'alabaster' diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 0000000..8e21750 --- /dev/null +++ b/docs/history.rst @@ -0,0 +1,8 @@ +:tocdepth: 2 + +.. _changes: + +History +******* + +.. include:: ../CHANGES (links).rst diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..c43329a --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,17 @@ +Welcome to |project| documentation! +=================================== + +.. toctree:: + :maxdepth: 1 + + history + +.. include:: ../README.rst + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..976ba02 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +ignore_missing_imports = True @@ -0,0 +1,222 @@ +""" +Implementation +""" + +import os as _os +import shlex as _shlex +import contextlib as _contextlib +import sys as _sys +import operator as _operator +import itertools as _itertools +import warnings as _warnings + +try: + # ensure that map has the same meaning on Python 2 + from future_builtins import map +except ImportError: + pass + +import pkg_resources +import setuptools.command.test as orig +from setuptools import Distribution + + +@_contextlib.contextmanager +def _save_argv(repl=None): + saved = _sys.argv[:] + if repl is not None: + _sys.argv[:] = repl + try: + yield saved + finally: + _sys.argv[:] = saved + + +class CustomizedDist(Distribution): + + allow_hosts = None + index_url = None + + def fetch_build_egg(self, req): + """Specialized version of Distribution.fetch_build_egg + that respects respects allow_hosts and index_url.""" + from setuptools.command.easy_install import easy_install + + dist = Distribution({'script_args': ['easy_install']}) + dist.parse_config_files() + opts = dist.get_option_dict('easy_install') + keep = ( + 'find_links', + 'site_dirs', + 'index_url', + 'optimize', + 'site_dirs', + 'allow_hosts', + ) + for key in list(opts): + if key not in keep: + del opts[key] # don't use any other settings + if self.dependency_links: + links = self.dependency_links[:] + if 'find_links' in opts: + links = opts['find_links'][1].split() + links + opts['find_links'] = ('setup', links) + if self.allow_hosts: + opts['allow_hosts'] = ('test', self.allow_hosts) + if self.index_url: + opts['index_url'] = ('test', self.index_url) + install_dir_func = getattr(self, 'get_egg_cache_dir', _os.getcwd) + install_dir = install_dir_func() + cmd = easy_install( + dist, + args=["x"], + install_dir=install_dir, + exclude_scripts=True, + always_copy=False, + build_directory=None, + editable=False, + upgrade=False, + multi_version=True, + no_report=True, + user=False, + ) + cmd.ensure_finalized() + return cmd.easy_install(req) + + +class PyTest(orig.test): + """ + >>> import setuptools + >>> dist = setuptools.Distribution() + >>> cmd = PyTest(dist) + """ + + user_options = [ + ('extras', None, "Install (all) setuptools extras when running tests"), + ( + 'index-url=', + None, + "Specify an index url from which to retrieve " "dependencies", + ), + ( + 'allow-hosts=', + None, + "Whitelist of comma-separated hosts to allow " + "when retrieving dependencies", + ), + ( + 'addopts=', + None, + "Additional options to be passed verbatim to the " "pytest runner", + ), + ] + + def initialize_options(self): + self.extras = False + self.index_url = None + self.allow_hosts = None + self.addopts = [] + self.ensure_setuptools_version() + + @staticmethod + def ensure_setuptools_version(): + """ + Due to the fact that pytest-runner is often required (via + setup-requires directive) by toolchains that never invoke + it (i.e. they're only installing the package, not testing it), + instead of declaring the dependency in the package + metadata, assert the requirement at run time. + """ + pkg_resources.require('setuptools>=27.3') + + def finalize_options(self): + if self.addopts: + self.addopts = _shlex.split(self.addopts) + + @staticmethod + def marker_passes(marker): + """ + Given an environment marker, return True if the marker is valid + and matches this environment. + """ + return ( + not marker + or not pkg_resources.invalid_marker(marker) + and pkg_resources.evaluate_marker(marker) + ) + + def install_dists(self, dist): + """ + Extend install_dists to include extras support + """ + return _itertools.chain( + orig.test.install_dists(dist), self.install_extra_dists(dist) + ) + + def install_extra_dists(self, dist): + """ + Install extras that are indicated by markers or + install all extras if '--extras' is indicated. + """ + extras_require = dist.extras_require or {} + + spec_extras = ( + (spec.partition(':'), reqs) for spec, reqs in extras_require.items() + ) + matching_extras = ( + reqs + for (name, sep, marker), reqs in spec_extras + # include unnamed extras or all if self.extras indicated + if (not name or self.extras) + # never include extras that fail to pass marker eval + and self.marker_passes(marker) + ) + results = list(map(dist.fetch_build_eggs, matching_extras)) + return _itertools.chain.from_iterable(results) + + @staticmethod + def _warn_old_setuptools(): + msg = ( + "pytest-runner will stop working on this version of setuptools; " + "please upgrade to setuptools 30.4 or later or pin to " + "pytest-runner < 5." + ) + ver_str = pkg_resources.get_distribution('setuptools').version + ver = pkg_resources.parse_version(ver_str) + if ver < pkg_resources.parse_version('30.4'): + _warnings.warn(msg) + + def run(self): + """ + Override run to ensure requirements are available in this session (but + don't install them anywhere). + """ + self._warn_old_setuptools() + dist = CustomizedDist() + for attr in 'allow_hosts index_url'.split(): + setattr(dist, attr, getattr(self, attr)) + for attr in ( + 'dependency_links install_requires ' 'tests_require extras_require ' + ).split(): + setattr(dist, attr, getattr(self.distribution, attr)) + installed_dists = self.install_dists(dist) + if self.dry_run: + self.announce('skipping tests (dry run)') + return + paths = map(_operator.attrgetter('location'), installed_dists) + with self.paths_on_pythonpath(paths): + with self.project_on_sys_path(): + return self.run_tests() + + @property + def _argv(self): + return ['pytest'] + self.addopts + + def run_tests(self): + """ + Invoke pytest, replacing argv. Return result code. + """ + with _save_argv(_sys.argv[:1] + self.addopts): + result_code = __import__('pytest').main() + if result_code: + raise SystemExit(result_code) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b6ebc0b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[build-system] +requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4.1"] +build-backend = "setuptools.build_meta" + +[tool.black] +skip-string-normalization = true + +[tool.setuptools_scm] + +[pytest.enabler.black] +addopts = "--black" + +[pytest.enabler.mypy] +addopts = "--mypy" + +[pytest.enabler.flake8] +addopts = "--flake8" + +[pytest.enabler.cov] +addopts = "--cov" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..6bf69af --- /dev/null +++ b/pytest.ini @@ -0,0 +1,7 @@ +[pytest] +norecursedirs=dist build .tox .eggs +addopts=--doctest-modules +doctest_optionflags=ALLOW_UNICODE ELLIPSIS +# workaround for warning pytest-dev/pytest#6178 +junit_family=xunit2 +filterwarnings= diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..57ba2f7 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,63 @@ +[metadata] +license_files = + LICENSE +name = pytest-runner +author = Jason R. Coombs +author_email = jaraco@jaraco.com +description = Invoke py.test as distutils command with dependency resolution +long_description = file:README.rst +url = https://github.com/pytest-dev/pytest-runner/ +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Framework :: Pytest + +[options] +packages = find_namespace: +py_modules = ptr +include_package_data = true +python_requires = >=3.6 +install_requires = + # setuptools 27.3 is required at run time +setup_requires = setuptools_scm[toml] >= 3.4.1 + +[options.packages.find] +exclude = + build* + dist* + docs* + tests* + +[options.extras_require] +testing = + # upstream + pytest >= 4.6 + pytest-checkdocs >= 2.4 + pytest-flake8 + # python_implementation: workaround for jaraco/skeleton#22 + # python_version: workaround for python/typed_ast#156 + pytest-black >= 0.3.7; python_implementation != "PyPy" and python_version < "3.10" + pytest-cov + # python_implementation: workaround for jaraco/skeleton#22 + # python_version: workaround for python/typed_ast#156 + pytest-mypy; python_implementation != "PyPy" and python_version < "3.10" + pytest-enabler >= 1.0.1 + + # local + pytest-virtualenv + +docs = + # upstream + sphinx + jaraco.packaging >= 8.2 + rst.linker >= 1.9 + + # local + +[options.entry_points] +distutils.commands = + ptr = ptr:PyTest + pytest = ptr:PyTest diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e58e686 --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +import setuptools + + +compat = dict( + name='pytest-runner', + py_modules=['ptr'], + setup_requires=['setuptools_scm >= 1.15.0'], + entry_points={'distutils.commands': ['ptr = ptr:PyTest', 'pytest = ptr:PyTest']}, +) +""" +Because pytest-runner is frequently installed by +setup_requires and thus easy_install, and because +many systems still run with setuptools prior to +30.4 in which support for declarative config was +added, supply the basic metadata here. Ref #49. +""" + +if __name__ == "__main__": + setuptools.setup(**compat) diff --git a/tests/test_ptr.py b/tests/test_ptr.py new file mode 100644 index 0000000..f6eac16 --- /dev/null +++ b/tests/test_ptr.py @@ -0,0 +1,173 @@ +from __future__ import unicode_literals + +import io +import os +import shutil +import sys +import tarfile +import textwrap +import time +import itertools + +import pytest + + +def DALS(s): + "dedent and left-strip" + return textwrap.dedent(s).lstrip() + + +def make_sdist(dist_path, files): + """ + Create a simple sdist tarball at dist_path, containing the files + listed in ``files`` as ``(filename, content)`` tuples. + """ + + with tarfile.open(dist_path, 'w:gz') as dist: + for filename, content in files: + file_bytes = io.BytesIO(content.encode('utf-8')) + file_info = tarfile.TarInfo(name=filename) + file_info.size = len(file_bytes.getvalue()) + file_info.mtime = int(time.time()) + dist.addfile(file_info, fileobj=file_bytes) + + +@pytest.fixture +def venv(virtualenv): + yield virtualenv + # Workaround virtualenv not cleaning itself as it should... + virtualenv.delete = True + virtualenv.teardown() + + +setuptools_reqs = ( + ['setuptools', 'setuptools==27.3.0', 'setuptools==32.3.1', 'setuptools==36.3.0'] + if sys.version_info < (3, 7) + else ['setuptools', 'setuptools==38.4.1'] +) +args_variants = ['', '--extras'] + + +@pytest.mark.xfail('platform.system() == "Windows"') +@pytest.mark.parametrize( + 'setuptools_req, test_args', itertools.product(setuptools_reqs, args_variants) +) +def test_egg_fetcher(venv, setuptools_req, test_args): + test_args = test_args.split() + # Install pytest & pytest-runner. + venv.run('python setup.py develop', cwd=os.getcwd()) + venv.run('pip install pytest') + # Install setuptools version. + venv.run('pip install -U'.split() + [setuptools_req]) + # For debugging purposes. + venv.run('pip freeze --all') + # Prepare fake index. + index_dir = (venv.workspace / 'index').mkdir() + for n in range(5): + dist_name = 'barbazquux' + str(n + 1) + dist_version = '0.1' + dist_sdist = '%s-%s.tar.gz' % (dist_name, dist_version) + dist_dir = (index_dir / dist_name).mkdir() + make_sdist( + dist_dir / dist_sdist, + ( + ( + 'setup.py', + textwrap.dedent( + ''' + from setuptools import setup + setup( + name={dist_name!r}, + version={dist_version!r}, + py_modules=[{dist_name!r}], + ) + ''' + ).format(dist_name=dist_name, dist_version=dist_version), + ), + (dist_name + '.py', ''), + ), + ) + with (dist_dir / 'index.html').open('w') as fp: + fp.write( + DALS( + ''' + <!DOCTYPE html><html><body> + <a href="{dist_sdist}" rel="internal">{dist_sdist}</a><br/> + </body></html> + ''' + ).format(dist_sdist=dist_sdist) + ) + # Move barbazquux1 out of the index. + shutil.move(index_dir / 'barbazquux1', venv.workspace) + barbazquux1_link = ( + 'file://' + + str(venv.workspace.abspath()) + + '/barbazquux1/barbazquux1-0.1.tar.gz' + + '#egg=barbazquux1-0.1' + ) + # Prepare fake project. + project_dir = (venv.workspace / 'project-0.1').mkdir() + with open(project_dir / 'setup.py', 'w') as fp: + fp.write( + DALS( + ''' + from setuptools import setup + setup( + name='project', + version='0.1', + dependency_links = [ + {barbazquux1_link!r}, + ], + setup_requires=[ + 'pytest-runner', + ], + install_requires=[ + 'barbazquux1', + ], + tests_require=[ + 'pytest', + 'barbazquux2', + ], + extras_require={{ + ':"{sys_platform}" in sys_platform': 'barbazquux3', + ':"barbazquux" in sys_platform': 'barbazquux4', + 'extra': 'barbazquux5', + }} + ) + ''' + ).format(sys_platform=sys.platform, barbazquux1_link=barbazquux1_link) + ) + with open(project_dir / 'setup.cfg', 'w') as fp: + fp.write( + DALS( + ''' + [easy_install] + index_url = . + ''' + ) + ) + with open(project_dir / 'test_stuff.py', 'w') as fp: + fp.write( + DALS( + ''' + import pytest + + def test_stuff(): + import barbazquux1 + import barbazquux2 + import barbazquux3 + with pytest.raises(ImportError): + import barbazquux4 + if {importable_barbazquux5}: + import barbazquux5 + else: + with pytest.raises(ImportError): + import barbazquux5 + ''' + ).format(importable_barbazquux5=('--extras' in test_args)) + ) + # Run fake project tests. + cmd = 'python setup.py pytest'.split() + cmd += ['--index-url=' + index_dir.abspath()] + cmd += test_args + venv.run(cmd, cwd=project_dir) @@ -0,0 +1,40 @@ +[tox] +envlist = python +minversion = 3.2 +# https://github.com/jaraco/skeleton/issues/6 +tox_pip_extensions_ext_venv_update = true +toxworkdir={env:TOX_WORK_DIR:.tox} + + +[testenv] +deps = +commands = + pytest {posargs} +usedevelop = True +extras = testing + +[testenv:docs] +extras = + docs + testing +changedir = docs +commands = + python -m sphinx -W --keep-going . {toxinidir}/build/html + +[testenv:release] +skip_install = True +deps = + build + twine>=3 + path + jaraco.develop>=7.1 +passenv = + TWINE_PASSWORD + GITHUB_TOKEN +setenv = + TWINE_USERNAME = {env:TWINE_USERNAME:__token__} +commands = + python -c "import path; path.Path('dist').rmtree_p()" + python -m build + python -m twine upload dist/* + python -m jaraco.develop.create-github-release |