Skip to content

Commit 6f407ef

Browse files
Merge pull request #2411 from nicoddemus/automate-pre-release
Automate pre release steps
2 parents 3c41349 + feab3ba commit 6f407ef

File tree

8 files changed

+95
-34
lines changed

8 files changed

+95
-34
lines changed

HOWTORELEASE.rst

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
How to release pytest
22
--------------------------------------------
33

4-
Note: this assumes you have already registered on PyPI and you have
5-
`invoke <https://pypi.org/project/invoke/>`_ installed.
4+
.. important::
65

7-
#. Check and finalize ``CHANGELOG.rst``.
6+
pytest releases must be prepared on **linux** because the docs and examples expect
7+
to be executed in that platform.
88

9-
#. Generate a new release announcement::
9+
#. Install development dependencies in a virtual environment with::
1010

11-
invoke generate.announce VERSION
11+
pip3 install -r tasks/requirements.txt
1212

13-
Feel free to modify the generated files before committing.
13+
#. Create a branch ``release-X.Y.Z`` with the version for the release. Make sure it is up to date
14+
with the latest ``master`` (for patch releases) and with the latest ``features`` merged with
15+
the latest ``master`` (for minor releases). Ensure your are in a clean work tree.
1416

15-
#. Regenerate the docs examples using tox::
17+
#. Check and finalize ``CHANGELOG.rst`` (will be automated soon).
1618

17-
tox -e regen
19+
#. Execute to automatically generate docs, announcements and upload a package to
20+
your ``devpi`` staging server::
1821

19-
#. At this point, open a PR named ``release-X`` so others can help find regressions or provide suggestions.
22+
invoke generate.pre_release <VERSION> <DEVPI USER> --password <DEVPI PASSWORD>
2023

21-
#. Use devpi for uploading a release tarball to a staging area::
22-
23-
devpi use https://devpi.net/USER/dev
24-
devpi upload --formats sdist,bdist_wheel
24+
If ``--password`` is not given, it is assumed the user is already logged in. If you don't have
25+
an account, please ask for one!
2526

2627
#. Run from multiple machines::
2728

doc/en/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ clean:
4141
-rm -rf $(BUILDDIR)/*
4242

4343
regen:
44-
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-p\ no:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
44+
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
4545

4646
html:
4747
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

doc/en/parametrize.rst

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,16 @@
99
Parametrizing fixtures and test functions
1010
==========================================================================
1111

12-
pytest supports test parametrization in several well-integrated ways:
12+
pytest enables test parametrization at several levels:
1313

14-
- :py:func:`pytest.fixture` allows to define :ref:`parametrization
15-
at the level of fixture functions <fixture-parametrize>`.
14+
- :py:func:`pytest.fixture` allows one to :ref:`parametrize fixture
15+
functions <fixture-parametrize>`.
1616

17-
* `@pytest.mark.parametrize`_ allows to define parametrization at the
18-
function or class level, provides multiple argument/fixture sets
19-
for a particular test function or class.
17+
* `@pytest.mark.parametrize`_ allows one to define multiple sets of
18+
arguments and fixtures at the test function or class.
2019

21-
* `pytest_generate_tests`_ enables implementing your own custom
22-
dynamic parametrization scheme or extensions.
20+
* `pytest_generate_tests`_ allows one to define custom parametrization
21+
schemes or extensions.
2322

2423
.. _parametrizemark:
2524
.. _`@pytest.mark.parametrize`:

doc/en/unittest.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ creation of a per-test temporary directory::
171171
tmpdir.join("samplefile.ini").write("# testdata")
172172

173173
def test_method(self):
174-
s = open("samplefile.ini").read()
174+
with open("samplefile.ini") as f:
175+
s = f.read()
175176
assert "testdata" in s
176177

177178
Due to the ``autouse`` flag the ``initdir`` fixture function will be

doc/en/warnings.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Running pytest now produces this output::
3939
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
4040
them into errors::
4141

42-
$ pytest -q test_show_warning.py -W error::DeprecationWarning
42+
$ pytest -q test_show_warnings.py -W error::DeprecationWarning
4343
4444
no tests ran in 0.12 seconds
4545
ERROR: file not found: test_show_warning.py

tasks/generate.py

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import os
12
from pathlib import Path
2-
from subprocess import check_output
3+
from subprocess import check_output, check_call
34

45
import invoke
56

@@ -9,11 +10,7 @@
910
})
1011
def announce(ctx, version):
1112
"""Generates a new release announcement entry in the docs."""
12-
print("[generate.announce] Generating Announce")
13-
1413
# Get our list of authors
15-
print("[generate.announce] Collecting author names")
16-
1714
stdout = check_output(["git", "describe", "--abbrev=0", '--tags'])
1815
stdout = stdout.decode('utf-8')
1916
last_version = stdout.strip()
@@ -29,12 +26,12 @@ def announce(ctx, version):
2926
contributors_text = '\n'.join('* {}'.format(name) for name in sorted(contributors)) + '\n'
3027
text = template_text.format(version=version, contributors=contributors_text)
3128

32-
target = Path(__file__).joinpath('../../doc/en/announce/release-{}.rst'.format(version))
29+
target = Path(__file__).parent.joinpath('../doc/en/announce/release-{}.rst'.format(version))
3330
target.write_text(text, encoding='UTF-8')
3431
print("[generate.announce] Generated {}".format(target.name))
3532

3633
# Update index with the new release entry
37-
index_path = Path(__file__).joinpath('../../doc/en/announce/index.rst')
34+
index_path = Path(__file__).parent.joinpath('../doc/en/announce/index.rst')
3835
lines = index_path.read_text(encoding='UTF-8').splitlines()
3936
indent = ' '
4037
for index, line in enumerate(lines):
@@ -48,9 +45,68 @@ def announce(ctx, version):
4845
print("[generate.announce] Skip {} (already contains release)".format(index_path.name))
4946
break
5047

51-
print()
52-
print('Please review the generated files and commit with:')
53-
print(' git commit -a -m "Generate new release announcement for {}'.format(version))
48+
check_call(['git', 'add', str(target)])
49+
50+
51+
@invoke.task()
52+
def regen(ctx):
53+
"""Call regendoc tool to update examples and pytest output in the docs."""
54+
print("[generate.regen] Updating docs")
55+
check_call(['tox', '-e', 'regen'])
56+
57+
58+
@invoke.task()
59+
def make_tag(ctx, version):
60+
"""Create a new (local) tag for the release, only if the repository is clean."""
61+
from git import Repo
62+
63+
repo = Repo('.')
64+
if repo.is_dirty():
65+
print('Current repository is dirty. Please commit any changes and try again.')
66+
raise invoke.Exit(code=2)
67+
68+
tag_names = [x.name for x in repo.tags]
69+
if version in tag_names:
70+
print("[generate.make_tag] Delete existing tag {}".format(version))
71+
repo.delete_tag(version)
72+
73+
print("[generate.make_tag] Create tag {}".format(version))
74+
repo.create_tag(version)
5475

5576

77+
@invoke.task()
78+
def devpi_upload(ctx, version, user, password=None):
79+
"""Creates and uploads a package to devpi for testing."""
80+
if password:
81+
print("[generate.devpi_upload] devpi login {}".format(user))
82+
check_call(['devpi', 'login', user, '--password', password])
83+
84+
check_call(['devpi', 'use', 'https://devpi.net/{}/dev'.format(user)])
85+
86+
env = os.environ.copy()
87+
env['SETUPTOOLS_SCM_PRETEND_VERSION'] = version
88+
check_call(['devpi', 'upload', '--formats', 'sdist,bdist_wheel'], env=env)
89+
print("[generate.devpi_upload] package uploaded")
90+
91+
92+
@invoke.task(help={
93+
'version': 'version being released',
94+
'user': 'name of the user on devpi to stage the generated package',
95+
'password': 'user password on devpi to stage the generated package '
96+
'(if not given assumed logged in)',
97+
})
98+
def pre_release(ctx, version, user, password=None):
99+
"""Generates new docs, release announcements and uploads a new release to devpi for testing."""
100+
announce(ctx, version)
101+
regen(ctx)
102+
103+
msg = 'Preparing release version {}'.format(version)
104+
check_call(['git', 'commit', '-a', '-m', msg])
105+
106+
make_tag(ctx, version)
107+
108+
devpi_upload(ctx, version=version, user=user, password=password)
109+
110+
print()
111+
print('[generate.pre_release] Please push your branch and open a PR.')
56112

tasks/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
invoke
2+
tox
3+
gitpython

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ commands=
134134

135135
[testenv:regen]
136136
changedir=doc/en
137+
skipsdist=True
137138
basepython = python3.5
138139
deps=sphinx
139140
PyYAML

0 commit comments

Comments
 (0)