Add sanity check script for new version tags

Add a script to catch a few common errors in new version numbers to give
the user a chance to avoid them. Set up tox and testr to support tests
for the new script.

Change-Id: I2a48f3de3886821611f370e04c9be13ada67edbf
This commit is contained in:
Doug Hellmann 2015-01-14 11:24:39 -05:00
parent 654d07dad0
commit c3ea9bb9f2
7 changed files with 224 additions and 2 deletions

4
.testr.conf Normal file
View File

@ -0,0 +1,4 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -s ./tests . $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -66,6 +66,11 @@ then
title "Version $VERSION is already tagged in this repository"
read -s -p "Press Ctrl-C to cancel or Return to continue..."
else
title "Sanity checking $VERSION"
if ! $TOOLSDIR/sanity_check_version.py $VERSION $(git tag)
then
read -s -p "Press Ctrl-C to cancel or Return to continue..."
fi
TARGETSHA=`git log -1 $SHA --format='%H'`
title "Tagging $TARGETSHA as $VERSION"

116
sanity_check_version.py Executable file
View File

@ -0,0 +1,116 @@
#!/usr/bin/env python
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Check a proposed new release version against other existing versions.
"""
from __future__ import print_function
import argparse
import sys
def try_int(val):
try:
return int(val)
except ValueError:
return val
def parse_version(v):
parts = v.split('.')
if len(parts) < 3:
parts.append('0')
return [try_int(p) for p in parts]
def format_version(v):
return '.'.join(str(p) for p in v)
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'new_version',
help='the new version being proposed',
)
parser.add_argument(
'existing_versions',
nargs='*',
help='the existing versions in the repository',
)
args = parser.parse_args()
new_version = parse_version(args.new_version)
if len(new_version) < 3:
new_version = new_version + [0]
existing_versions = sorted([
parse_version(v)
for v in args.existing_versions
])
msgs = apply_rules(new_version, existing_versions)
for msg in msgs:
print(msg)
return 1 if msgs else 0
def apply_rules(new_version, existing_versions):
if not existing_versions:
if new_version[0] != 0:
return ['first version in repository does not start with 0']
return []
if new_version in existing_versions:
return ['version %r already exists in repository' %
format_version(new_version)]
same_minor = same_major = None
for v in existing_versions:
# Look for other numbers in the series
if v[:2] == new_version[:2]:
print('%r in same minor series as %r' %
(format_version(v), format_version(new_version)))
if not same_minor or v > same_minor:
same_minor = v
if v[:1] == new_version[:1]:
print('%r in same major series as %r' %
(format_version(v), format_version(new_version)))
if not same_major or v > same_major:
same_major = v
if same_minor is not None:
print('last version in minor series %r' %
format_version(same_minor))
actual = same_minor[2] + 1
expected = new_version[2]
if expected != actual:
return ['new version %r increments patch version more than one over %r' %
(format_version(new_version), format_version(same_minor))]
if same_major is not None and same_major != same_minor:
print('last version in major series %r' %
format_version(same_major))
if new_version > same_major:
actual = same_major[1] + 1
expected = new_version[1]
if actual > expected:
return ['new version %r increments minor version more than one over %r' %
(format_version(new_version), format_version(same_major))]
if new_version[2] != 0:
return ['new version %r increments minor version and patch version' %
format_version(new_version)]
latest_version = existing_versions[-1]
if new_version[0] > latest_version[0]:
return ['%r is a major version increment over %r' %
(format_version(new_version), format_version(latest_version))]
return []
if __name__ == '__main__':
sys.exit(main())

View File

@ -9,3 +9,4 @@ oslo.sphinx
testrepository>=0.0.17
testscenarios>=0.4
testtools>=0.9.32
oslotest

0
tests/__init__.py Normal file
View File

View File

@ -0,0 +1,95 @@
#!/usr/bin/env python
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslotest import base
import testscenarios
import sanity_check_version as scv
load_tests = testscenarios.load_tests_apply_scenarios
class ParseVersionTest(base.BaseTestCase):
def test_all_ints(self):
self.assertEqual([1, 0, 1], scv.parse_version('1.0.1'))
def test_not_int(self):
self.assertEqual([1, 'a', 1], scv.parse_version('1.a.1'))
def test_short(self):
self.assertEqual([1, 1, 0], scv.parse_version('1.1'))
class RulesTest(base.BaseTestCase):
scenarios = [
('no existing versions, 1.0.0',
{'new_version': [1, 0, 0],
'existing_versions': [],
'expected': ['first version in repository does not start with 0']}),
('no existing versions, 0.1.0',
{'new_version': [0, 1, 0],
'existing_versions': [],
'expected': []}),
('existing series, next minor number',
{'new_version': [1, 1, 0],
'existing_versions': [[0, 1, 0], [1, 0, 0]],
'expected': []}),
('existing series, next patch number',
{'new_version': [1, 1, 2],
'existing_versions': [[0, 1, 0], [1, 0, 0], [1, 1, 1]],
'expected': []}),
('existing series, next patch number in existing minor release',
{'new_version': [1, 1, 2],
'existing_versions': [[0, 1, 0], [1, 0, 0], [1, 1, 1], [1, 2, 0]],
'expected': []}),
('existing series, extra patch number',
{'new_version': [1, 1, 3],
'existing_versions': [[0, 1, 0], [1, 0, 0], [1, 1, 1]],
'expected': ["new version '1.1.3' increments patch version more than one over '1.1.1'"]}),
('existing series, extra patch number in existing minor release',
{'new_version': [1, 1, 3],
'existing_versions': [[0, 1, 0], [1, 0, 0], [1, 1, 1], [1, 2, 0]],
'expected': ["new version '1.1.3' increments patch version more than one over '1.1.1'"]}),
('next major number',
{'new_version': [2, 0, 0],
'existing_versions': [[0, 1, 0], [1, 0, 0]],
'expected': ["'2.0.0' is a major version increment over '1.0.0'"]}),
# Taken from oslo.config
('next minor, with name tags',
{'new_version': [1, 7, 0],
'existing_versions': [
[1, 4, 0, '0a5'],
[1, 5, 0],
[1, 6, 0],
[2013, '1b1'],
[2013, '1b2'],
[2013, '1b3'],
[2013, '1b4'],
[2013, '1b5'],
['grizzly-eol'],
['havana-eol'],
],
'expected': []}),
]
def test(self):
msgs = scv.apply_rules(self.new_version, self.existing_versions)
self.assertEqual(self.expected, msgs)

View File

@ -1,6 +1,6 @@
[tox]
minversion = 1.6
envlist = py26,py27,pypy,pep8
envlist = py27,pep8
skipsdist = True
[testenv]
@ -12,9 +12,10 @@ setenv =
VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
#commands = python setup.py testr --slowest --testr-args='{posargs}'
commands = python setup.py testr --slowest --testr-args='{posargs}'
[testenv:pep8]
#deps = flake8
#commands = flake8
[testenv:venv]