summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.coveragerc5
-rw-r--r--.flake89
-rw-r--r--.github/FUNDING.yml2
-rw-r--r--.pre-commit-config.yaml5
-rw-r--r--.readthedocs.yml5
-rw-r--r--.travis.yml28
-rw-r--r--CHANGES.rst219
-rw-r--r--LICENSE7
-rw-r--r--README.rst136
-rw-r--r--appveyor.yml24
-rw-r--r--docs/conf.py30
-rw-r--r--docs/history.rst8
-rw-r--r--docs/index.rst17
-rw-r--r--ptr.py222
-rw-r--r--pyproject.toml6
-rw-r--r--pytest.ini11
-rw-r--r--setup.cfg50
-rw-r--r--setup.py21
-rw-r--r--skeleton.md137
-rw-r--r--tests/test_ptr.py172
-rw-r--r--tox.ini38
21 files changed, 1149 insertions, 3 deletions
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..4582306
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,5 @@
+[run]
+omit = .tox/*
+
+[report]
+show_missing = True
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..790c109
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,9 @@
+[flake8]
+max-line-length = 88
+ignore =
+ # W503 violates spec https://github.com/PyCQA/pycodestyle/issues/513
+ W503
+ # W504 has issues https://github.com/OCA/maintainer-quality-tools/issues/545
+ W504
+ # 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/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..922d942
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,5 @@
+repos:
+- repo: https://github.com/ambv/black
+ rev: 18.9b0
+ hooks:
+ - id: black
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 0000000..8ae4468
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,5 @@
+python:
+ version: 3
+ extra_requirements:
+ - docs
+ pip_install: true
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6ccac8f
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,28 @@
+dist: xenial
+language: python
+
+python:
+- 2.7
+- 3.6
+- &latest_py3 3.7
+
+jobs:
+ fast_finish: true
+ include:
+ - stage: deploy
+ if: tag IS present
+ python: *latest_py3
+ before_script: skip
+ script: tox -e release
+
+cache: pip
+
+install:
+- pip install tox tox-venv
+
+before_script:
+ # Disable IPv6. Ref travis-ci/travis-ci#8361
+ - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
+ sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6';
+ fi
+script: tox
diff --git a/CHANGES.rst b/CHANGES.rst
new file mode 100644
index 0000000..08fc659
--- /dev/null
+++ b/CHANGES.rst
@@ -0,0 +1,219 @@
+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.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..5e795a6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,7 @@
+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.
diff --git a/README.rst b/README.rst
index 420bfb4..ba9805b 100644
--- a/README.rst
+++ b/README.rst
@@ -1,5 +1,137 @@
-.. 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: https://pypi.org/project/pytest-runner
+
+.. image:: https://img.shields.io/pypi/pyversions/pytest-runner.svg
+
+.. image:: https://img.shields.io/travis/pytest-dev/pytest-runner/master.svg
+ :target: https://travis-ci.org/pytest-dev/pytest-runner
+
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+ :target: https://github.com/ambv/black
+ :alt: Code style: Black
+
+.. .. image:: https://img.shields.io/appveyor/ci/pytest-dev/pytest-runner/master.svg
+.. :target: https://ci.appveyor.com/project/pytest-dev/pytest-runner/branch/master
+
+.. .. image:: https://readthedocs.org/projects/pytest-runner/badge/?version=latest
+.. :target: https://pytest-runner.readthedocs.io/en/latest/?badge=latest
+
+.. 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 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 `setup_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 rwt
+<https://pypi.org/project/rwt>`_.
+
+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,
+ )
Security Contact
================
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..f35aa27
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,24 @@
+environment:
+
+ APPVEYOR: true
+
+ matrix:
+ - PYTHON: "C:\\Python36-x64"
+ - PYTHON: "C:\\Python27-x64"
+
+install:
+ # symlink python from a directory with a space
+ - "mklink /d \"C:\\Program Files\\Python\" %PYTHON%"
+ - "SET PYTHON=\"C:\\Program Files\\Python\""
+ - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
+
+build: off
+
+cache:
+ - '%LOCALAPPDATA%\pip\Cache'
+
+test_script:
+ - "python -m pip install -U tox tox-venv virtualenv"
+ - "tox"
+
+version: '{build}'
diff --git a/docs/conf.py b/docs/conf.py
index dbf962d..e06bee4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,3 +1,33 @@
+#!/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}/',
+ ),
+ ],
+ )
+}
# 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..ae93273
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,17 @@
+Welcome to pytest-runner documentation!
+=======================================
+
+.. toctree::
+ :maxdepth: 1
+
+ history
+
+.. include:: ../README.rst
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/ptr.py b/ptr.py
new file mode 100644
index 0000000..f3952e8
--- /dev/null
+++ b/ptr.py
@@ -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..3afc8c3
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,6 @@
+[build-system]
+requires = ["setuptools>=34.4", "wheel", "setuptools_scm>=1.15"]
+build-backend = "setuptools.build_meta"
+
+[tool.black]
+skip-string-normalization = true
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..a86fb66
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,11 @@
+[pytest]
+norecursedirs=dist build .tox .eggs
+addopts=--doctest-modules --flake8 --black --cov
+doctest_optionflags=ALLOW_UNICODE ELLIPSIS
+filterwarnings=
+ ignore:Possible nested set::pycodestyle:113
+ ignore:Using or importing the ABCs::flake8:410
+ # workaround for https://sourceforge.net/p/docutils/bugs/348/
+ ignore:'U' mode is deprecated::docutils.io
+ # workaround for https://gitlab.com/pycqa/flake8/issues/275
+ ignore:You passed a bytestring as `filenames`.::flake8
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..931d36d
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,50 @@
+[bdist_wheel]
+universal = 1
+
+[metadata]
+license_file = 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 :: 2.7
+ Programming Language :: Python :: 3
+ Framework :: Pytest
+
+[options]
+py_modules = ptr
+python_requires = >=2.7
+install_requires =
+ # setuptools 27.3 is required at run time
+setup_requires = setuptools_scm >= 1.15.0
+
+[options.extras_require]
+testing =
+ # upstream
+ pytest >= 3.5, !=3.7.3
+ pytest-checkdocs
+ pytest-flake8
+ pytest-black-multipy
+ pytest-cov
+
+ # local
+ pytest-virtualenv
+
+docs =
+ # upstream
+ sphinx
+ jaraco.packaging >= 3.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..d6459a0
--- /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(use_scm_version=True, **compat)
diff --git a/skeleton.md b/skeleton.md
new file mode 100644
index 0000000..52b97f0
--- /dev/null
+++ b/skeleton.md
@@ -0,0 +1,137 @@
+# Overview
+
+This project is merged with [skeleton](https://github.com/jaraco/skeleton). What is skeleton? It's the scaffolding of a Python project jaraco [introduced in his blog](https://blog.jaraco.com/a-project-skeleton-for-python-projects/). It seeks to provide a means to re-use techniques and inherit advances when managing projects for distribution.
+
+## An SCM Managed Approach
+
+While maintaining dozens of projects in PyPI, jaraco derives best practices for project distribution and publishes them in the [skeleton repo](https://github.com/jaraco/skeleton), a git repo capturing the evolution and culmination of these best practices.
+
+It's intended to be used by a new or existing project to adopt these practices and honed and proven techniques. Adopters are encouraged to use the project directly and maintain a small deviation from the technique, make their own fork for more substantial changes unique to their environment or preferences, or simply adopt the skeleton once and abandon it thereafter.
+
+The primary advantage to using an SCM for maintaining these techniques is that those tools help facilitate the merge between the template and its adopting projects.
+
+Another advantage to using an SCM-managed approach is that tools like GitHub recognize that a change in the skeleton is the _same change_ across all projects that merge with that skeleton. Without the ancestry, with a traditional copy/paste approach, a [commit like this](https://github.com/jaraco/skeleton/commit/12eed1326e1bc26ce256e7b3f8cd8d3a5beab2d5) would produce notifications in the upstream project issue for each and every application, but because it's centralized, GitHub provides just the one notification when the change is added to the skeleton.
+
+# Usage
+
+## new projects
+
+To use skeleton for a new project, simply pull the skeleton into a new project:
+
+```
+$ git init my-new-project
+$ cd my-new-project
+$ git pull gh://jaraco/skeleton
+```
+
+Now customize the project to suit your individual project needs.
+
+## existing projects
+
+If you have an existing project, you can still incorporate the skeleton by merging it into the codebase.
+
+```
+$ git merge skeleton --allow-unrelated-histories
+```
+
+The `--allow-unrelated-histories` is necessary because the history from the skeleton was previously unrelated to the existing codebase. Resolve any merge conflicts and commit to the master, and now the project is based on the shared skeleton.
+
+## Updating
+
+Whenever a change is needed or desired for the general technique for packaging, it can be made in the skeleton project and then merged into each of the derived projects as needed, recommended before each release. As a result, features and best practices for packaging are centrally maintained and readily trickle into a whole suite of packages. This technique lowers the amount of tedious work necessary to create or maintain a project, and coupled with other techniques like continuous integration and deployment, lowers the cost of creating and maintaining refined Python projects to just a few, familiar git operations.
+
+Thereafter, the target project can make whatever customizations it deems relevant to the scaffolding. The project may even at some point decide that the divergence is too great to merit renewed merging with the original skeleton. This approach applies maximal guidance while creating minimal constraints.
+
+# Features
+
+The features/techniques employed by the skeleton include:
+
+- PEP 517/518 based build relying on setuptools as the build tool
+- setuptools declarative configuration using setup.cfg
+- tox for running tests
+- A README.rst as reStructuredText with some popular badges, but with readthedocs and appveyor badges commented out
+- A CHANGES.rst file intended for publishing release notes about the project
+- Use of [black](https://black.readthedocs.io/en/stable/) for code formatting (disabled on unsupported Python 3.5 and earlier)
+
+## Packaging Conventions
+
+A pyproject.toml is included to enable PEP 517 and PEP 518 compatibility and declares the requirements necessary to build the project on setuptools (a minimum version compatible with setup.cfg declarative config).
+
+The setup.cfg file implements the following features:
+
+- Assumes universal wheel for release
+- Advertises the project's LICENSE file (MIT by default)
+- Reads the README.rst file into the long description
+- Some common Trove classifiers
+- Includes all packages discovered in the repo
+- Data files in the package are also included (not just Python files)
+- Declares the required Python versions
+- Declares install requirements (empty by default)
+- Declares setup requirements for legacy environments
+- Supplies two 'extras':
+ - testing: requirements for running tests
+ - docs: requirements for building docs
+ - these extras split the declaration into "upstream" (requirements as declared by the skeleton) and "local" (those specific to the local project); these markers help avoid merge conflicts
+- Placeholder for defining entry points
+
+Additionally, the setup.py file declares `use_scm_version` which relies on [setuptools_scm](https://pypi.org/project/setuptools_scm) to do two things:
+
+- derive the project version from SCM tags
+- ensure that all files committed to the repo are automatically included in releases
+
+## Running Tests
+
+The skeleton assumes the developer has [tox](https://pypi.org/project/tox) installed. The developer is expected to run `tox` to run tests on the current Python version using [pytest](https://pypi.org/project/pytest).
+
+Other environments (invoked with `tox -e {name}`) supplied include:
+
+ - a `build-docs` environment to build the documentation
+ - a `release` environment to publish the package to PyPI
+
+A pytest.ini is included to define common options around running tests. In particular:
+
+- rely on default test discovery in the current directory
+- avoid recursing into common directories not containing tests
+- run doctests on modules and invoke flake8 tests
+- in doctests, allow unicode literals and regular literals to match, allowing for doctests to run on Python 2 and 3. Also enable ELLIPSES, a default that would be undone by supplying the prior option.
+- filters out known warnings caused by libraries/functionality included by the skeleton
+
+Relies a .flake8 file to correct some default behaviors:
+
+- disable mutually incompatible rules W503 and W504
+- support for black format
+
+## Continuous Integration
+
+The project is pre-configured to run tests in [Travis-CI](https://travis-ci.org) (.travis.yml). Any new project must be enabled either through their web site or with the `travis enable` command.
+
+Features include:
+- test against Python 2 and 3
+- run on Ubuntu Xenial
+- correct for broken IPv6
+
+Also provided is a minimal template for running under Appveyor (Windows).
+
+### Continuous Deployments
+
+In addition to running tests, an additional deploy stage is configured to automatically release tagged commits to PyPI using [API tokens](https://pypi.org/help/#apitoken). The release process expects an authorized token to be configured with Travis as the TWINE_PASSWORD environment variable. After the Travis project is created, configure the token through the web UI or with a command like the following (bash syntax):
+
+```
+TWINE_PASSWORD={token} travis env copy TWINE_PASSWORD
+```
+
+## Building Documentation
+
+Documentation is automatically built by [Read the Docs](https://readthedocs.org) when the project is registered with it, by way of the .readthedocs.yml file. To test the docs build manually, a tox env may be invoked as `tox -e build-docs`. Both techniques rely on the dependencies declared in `setup.cfg/options.extras_require.docs`.
+
+In addition to building the sphinx docs scaffolded in `docs/`, the docs build a `history.html` file that first injects release dates and hyperlinks into the CHANGES.rst before incorporating it as history in the docs.
+
+## Cutting releases
+
+By default, tagged commits are released through the continuous integration deploy stage.
+
+Releases may also be cut manually by invoking the tox environment `release` with the PyPI token set as the TWINE_PASSWORD:
+
+```
+TWINE_PASSWORD={token} tox -e release
+```
diff --git a/tests/test_ptr.py b/tests/test_ptr.py
new file mode 100644
index 0000000..07633b0
--- /dev/null
+++ b/tests/test_ptr.py
@@ -0,0 +1,172 @@
+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.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)
diff --git a/tox.ini b/tox.ini
index 3505351..2e22bd3 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,45 @@
+[tox]
+envlist = python
+minversion = 3.2
+# https://github.com/jaraco/skeleton/issues/6
+tox_pip_extensions_ext_venv_update = true
+# Ensure that a late version of pip is used even on tox-venv.
+requires =
+ tox-pip-version>=0.0.6
+ tox-venv
+
+
+[testenv]
+deps =
+ setuptools>=31.0.1
+pip_version = pip
+commands =
+ pytest {posargs}
+usedevelop = True
+extras = testing
+
+[testenv:build-docs]
+extras =
+ docs
+ testing
+changedir = docs
+commands =
+ python -m sphinx . {toxinidir}/build/html
+
[testenv:release]
+skip_install = True
deps =
+ pep517>=0.5
+ twine>=1.13
+ path.py
jaraco.tidelift
passenv =
+ TWINE_PASSWORD
TIDELIFT_TOKEN
+setenv =
+ TWINE_USERNAME = {env:TWINE_USERNAME:__token__}
commands =
+ python -c "import path; path.Path('dist').rmtree_p()"
+ python -m pep517.build .
+ python -m twine upload dist/*
python -m jaraco.tidelift.publish-release-notes