Merge pull request #2411 from nicoddemus/automate-pre-release
Automate pre release steps
This commit is contained in:
commit
6f407ef308
|
@ -1,27 +1,28 @@
|
||||||
How to release pytest
|
How to release pytest
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
Note: this assumes you have already registered on PyPI and you have
|
.. important::
|
||||||
`invoke <https://pypi.org/project/invoke/>`_ installed.
|
|
||||||
|
|
||||||
#. Check and finalize ``CHANGELOG.rst``.
|
pytest releases must be prepared on **linux** because the docs and examples expect
|
||||||
|
to be executed in that platform.
|
||||||
|
|
||||||
#. Generate a new release announcement::
|
#. Install development dependencies in a virtual environment with::
|
||||||
|
|
||||||
invoke generate.announce VERSION
|
pip3 install -r tasks/requirements.txt
|
||||||
|
|
||||||
Feel free to modify the generated files before committing.
|
#. Create a branch ``release-X.Y.Z`` with the version for the release. Make sure it is up to date
|
||||||
|
with the latest ``master`` (for patch releases) and with the latest ``features`` merged with
|
||||||
|
the latest ``master`` (for minor releases). Ensure your are in a clean work tree.
|
||||||
|
|
||||||
#. Regenerate the docs examples using tox::
|
#. Check and finalize ``CHANGELOG.rst`` (will be automated soon).
|
||||||
|
|
||||||
tox -e regen
|
#. Execute to automatically generate docs, announcements and upload a package to
|
||||||
|
your ``devpi`` staging server::
|
||||||
|
|
||||||
#. At this point, open a PR named ``release-X`` so others can help find regressions or provide suggestions.
|
invoke generate.pre_release <VERSION> <DEVPI USER> --password <DEVPI PASSWORD>
|
||||||
|
|
||||||
#. Use devpi for uploading a release tarball to a staging area::
|
If ``--password`` is not given, it is assumed the user is already logged in. If you don't have
|
||||||
|
an account, please ask for one!
|
||||||
devpi use https://devpi.net/USER/dev
|
|
||||||
devpi upload --formats sdist,bdist_wheel
|
|
||||||
|
|
||||||
#. Run from multiple machines::
|
#. Run from multiple machines::
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ clean:
|
||||||
-rm -rf $(BUILDDIR)/*
|
-rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
regen:
|
regen:
|
||||||
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-p\ no:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
|
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
|
||||||
|
|
||||||
html:
|
html:
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
|
|
|
@ -9,17 +9,16 @@
|
||||||
Parametrizing fixtures and test functions
|
Parametrizing fixtures and test functions
|
||||||
==========================================================================
|
==========================================================================
|
||||||
|
|
||||||
pytest supports test parametrization in several well-integrated ways:
|
pytest enables test parametrization at several levels:
|
||||||
|
|
||||||
- :py:func:`pytest.fixture` allows to define :ref:`parametrization
|
- :py:func:`pytest.fixture` allows one to :ref:`parametrize fixture
|
||||||
at the level of fixture functions <fixture-parametrize>`.
|
functions <fixture-parametrize>`.
|
||||||
|
|
||||||
* `@pytest.mark.parametrize`_ allows to define parametrization at the
|
* `@pytest.mark.parametrize`_ allows one to define multiple sets of
|
||||||
function or class level, provides multiple argument/fixture sets
|
arguments and fixtures at the test function or class.
|
||||||
for a particular test function or class.
|
|
||||||
|
|
||||||
* `pytest_generate_tests`_ enables implementing your own custom
|
* `pytest_generate_tests`_ allows one to define custom parametrization
|
||||||
dynamic parametrization scheme or extensions.
|
schemes or extensions.
|
||||||
|
|
||||||
.. _parametrizemark:
|
.. _parametrizemark:
|
||||||
.. _`@pytest.mark.parametrize`:
|
.. _`@pytest.mark.parametrize`:
|
||||||
|
|
|
@ -171,7 +171,8 @@ creation of a per-test temporary directory::
|
||||||
tmpdir.join("samplefile.ini").write("# testdata")
|
tmpdir.join("samplefile.ini").write("# testdata")
|
||||||
|
|
||||||
def test_method(self):
|
def test_method(self):
|
||||||
s = open("samplefile.ini").read()
|
with open("samplefile.ini") as f:
|
||||||
|
s = f.read()
|
||||||
assert "testdata" in s
|
assert "testdata" in s
|
||||||
|
|
||||||
Due to the ``autouse`` flag the ``initdir`` fixture function will be
|
Due to the ``autouse`` flag the ``initdir`` fixture function will be
|
||||||
|
|
|
@ -39,7 +39,7 @@ Running pytest now produces this output::
|
||||||
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
|
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
|
||||||
them into errors::
|
them into errors::
|
||||||
|
|
||||||
$ pytest -q test_show_warning.py -W error::DeprecationWarning
|
$ pytest -q test_show_warnings.py -W error::DeprecationWarning
|
||||||
|
|
||||||
no tests ran in 0.12 seconds
|
no tests ran in 0.12 seconds
|
||||||
ERROR: file not found: test_show_warning.py
|
ERROR: file not found: test_show_warning.py
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import check_output
|
from subprocess import check_output, check_call
|
||||||
|
|
||||||
import invoke
|
import invoke
|
||||||
|
|
||||||
|
@ -9,11 +10,7 @@ import invoke
|
||||||
})
|
})
|
||||||
def announce(ctx, version):
|
def announce(ctx, version):
|
||||||
"""Generates a new release announcement entry in the docs."""
|
"""Generates a new release announcement entry in the docs."""
|
||||||
print("[generate.announce] Generating Announce")
|
|
||||||
|
|
||||||
# Get our list of authors
|
# Get our list of authors
|
||||||
print("[generate.announce] Collecting author names")
|
|
||||||
|
|
||||||
stdout = check_output(["git", "describe", "--abbrev=0", '--tags'])
|
stdout = check_output(["git", "describe", "--abbrev=0", '--tags'])
|
||||||
stdout = stdout.decode('utf-8')
|
stdout = stdout.decode('utf-8')
|
||||||
last_version = stdout.strip()
|
last_version = stdout.strip()
|
||||||
|
@ -29,12 +26,12 @@ def announce(ctx, version):
|
||||||
contributors_text = '\n'.join('* {}'.format(name) for name in sorted(contributors)) + '\n'
|
contributors_text = '\n'.join('* {}'.format(name) for name in sorted(contributors)) + '\n'
|
||||||
text = template_text.format(version=version, contributors=contributors_text)
|
text = template_text.format(version=version, contributors=contributors_text)
|
||||||
|
|
||||||
target = Path(__file__).joinpath('../../doc/en/announce/release-{}.rst'.format(version))
|
target = Path(__file__).parent.joinpath('../doc/en/announce/release-{}.rst'.format(version))
|
||||||
target.write_text(text, encoding='UTF-8')
|
target.write_text(text, encoding='UTF-8')
|
||||||
print("[generate.announce] Generated {}".format(target.name))
|
print("[generate.announce] Generated {}".format(target.name))
|
||||||
|
|
||||||
# Update index with the new release entry
|
# Update index with the new release entry
|
||||||
index_path = Path(__file__).joinpath('../../doc/en/announce/index.rst')
|
index_path = Path(__file__).parent.joinpath('../doc/en/announce/index.rst')
|
||||||
lines = index_path.read_text(encoding='UTF-8').splitlines()
|
lines = index_path.read_text(encoding='UTF-8').splitlines()
|
||||||
indent = ' '
|
indent = ' '
|
||||||
for index, line in enumerate(lines):
|
for index, line in enumerate(lines):
|
||||||
|
@ -48,9 +45,68 @@ def announce(ctx, version):
|
||||||
print("[generate.announce] Skip {} (already contains release)".format(index_path.name))
|
print("[generate.announce] Skip {} (already contains release)".format(index_path.name))
|
||||||
break
|
break
|
||||||
|
|
||||||
|
check_call(['git', 'add', str(target)])
|
||||||
|
|
||||||
|
|
||||||
|
@invoke.task()
|
||||||
|
def regen(ctx):
|
||||||
|
"""Call regendoc tool to update examples and pytest output in the docs."""
|
||||||
|
print("[generate.regen] Updating docs")
|
||||||
|
check_call(['tox', '-e', 'regen'])
|
||||||
|
|
||||||
|
|
||||||
|
@invoke.task()
|
||||||
|
def make_tag(ctx, version):
|
||||||
|
"""Create a new (local) tag for the release, only if the repository is clean."""
|
||||||
|
from git import Repo
|
||||||
|
|
||||||
|
repo = Repo('.')
|
||||||
|
if repo.is_dirty():
|
||||||
|
print('Current repository is dirty. Please commit any changes and try again.')
|
||||||
|
raise invoke.Exit(code=2)
|
||||||
|
|
||||||
|
tag_names = [x.name for x in repo.tags]
|
||||||
|
if version in tag_names:
|
||||||
|
print("[generate.make_tag] Delete existing tag {}".format(version))
|
||||||
|
repo.delete_tag(version)
|
||||||
|
|
||||||
|
print("[generate.make_tag] Create tag {}".format(version))
|
||||||
|
repo.create_tag(version)
|
||||||
|
|
||||||
|
|
||||||
|
@invoke.task()
|
||||||
|
def devpi_upload(ctx, version, user, password=None):
|
||||||
|
"""Creates and uploads a package to devpi for testing."""
|
||||||
|
if password:
|
||||||
|
print("[generate.devpi_upload] devpi login {}".format(user))
|
||||||
|
check_call(['devpi', 'login', user, '--password', password])
|
||||||
|
|
||||||
|
check_call(['devpi', 'use', 'https://devpi.net/{}/dev'.format(user)])
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['SETUPTOOLS_SCM_PRETEND_VERSION'] = version
|
||||||
|
check_call(['devpi', 'upload', '--formats', 'sdist,bdist_wheel'], env=env)
|
||||||
|
print("[generate.devpi_upload] package uploaded")
|
||||||
|
|
||||||
|
|
||||||
|
@invoke.task(help={
|
||||||
|
'version': 'version being released',
|
||||||
|
'user': 'name of the user on devpi to stage the generated package',
|
||||||
|
'password': 'user password on devpi to stage the generated package '
|
||||||
|
'(if not given assumed logged in)',
|
||||||
|
})
|
||||||
|
def pre_release(ctx, version, user, password=None):
|
||||||
|
"""Generates new docs, release announcements and uploads a new release to devpi for testing."""
|
||||||
|
announce(ctx, version)
|
||||||
|
regen(ctx)
|
||||||
|
|
||||||
|
msg = 'Preparing release version {}'.format(version)
|
||||||
|
check_call(['git', 'commit', '-a', '-m', msg])
|
||||||
|
|
||||||
|
make_tag(ctx, version)
|
||||||
|
|
||||||
|
devpi_upload(ctx, version=version, user=user, password=password)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print('Please review the generated files and commit with:')
|
print('[generate.pre_release] Please push your branch and open a PR.')
|
||||||
print(' git commit -a -m "Generate new release announcement for {}'.format(version))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
invoke
|
||||||
|
tox
|
||||||
|
gitpython
|
Loading…
Reference in New Issue