Update tox and tests to work with modern setups

While trying to make some changes I discovered that the tox
configuration for osc-placement was rather out of date and
functional tests were not working for python3. With this
change we bring tox.ini into a style that is more in keeping
with modern standards, use stestr, and update some functional
tests so they work with python3.

The functional tests changes are either:

* to fix the decoding of response
* to adapt argparse error response checking between python
  version.

These changes cascade some required change into how the gate-side
functional testing is performed. We make it explicit that in the
python2 job, the 'functional' tox job is run. When the python3
job is run, 'functional-py3' is run. Also stestr replaces
testr in the post-test hook.

When the functional-py3 test had been run in the past it
was actually running a python2 osc-placement against a
python3 devstack. We change that here to be python3 and
python3. Once that was happening, additional failures
were revealed, now fixed.

One particular issue was that while the json module
for python 3.6 and greater will decode strings or bytes,
the version of 3.5 will only do strings. We switch
to using simplejson throughout which smooths things over.
This is added as a new requirement but it isn't really:
simplejson is required by osc-lib.

Finally, some requirements need to be tuned to pass the
gate requirements job.

Change-Id: I999a3103dd85c0a437785766eef533875fca31fc
This commit is contained in:
Chris Dent 2019-03-01 00:05:57 +00:00 committed by Eric Fried
parent bffd59ea53
commit fc563d37bc
13 changed files with 84 additions and 49 deletions

3
.stestr.conf Normal file
View File

@ -0,0 +1,3 @@
[DEFAULT]
test_path=./osc_placement/tests/unit
top_dir=./

View File

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

View File

@ -11,10 +11,10 @@
# under the License. # under the License.
import contextlib import contextlib
import json
import keystoneauth1.exceptions.http as ks_exceptions import keystoneauth1.exceptions.http as ks_exceptions
import osc_lib.exceptions as exceptions import osc_lib.exceptions as exceptions
import simplejson as json
import six import six

View File

@ -10,15 +10,23 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import random import random
import six
import subprocess import subprocess
from oslotest import base from oslotest import base
import simplejson as json
RP_PREFIX = 'osc-placement-functional-tests-' RP_PREFIX = 'osc-placement-functional-tests-'
# argparse in python 2 and 3 have different error messages
ARGUMENTS_MISSING = 'too few arguments'
ARGUMENTS_REQUIRED = 'argument %s is required'
if six.PY3:
ARGUMENTS_MISSING = 'the following arguments are required'
ARGUMENTS_REQUIRED = 'the following arguments are required: %s'
class BaseTestCase(base.BaseTestCase): class BaseTestCase(base.BaseTestCase):
VERSION = None VERSION = None
@ -74,7 +82,7 @@ class BaseTestCase(base.BaseTestCase):
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
self.assertIn( self.assertIn(
message, e.output, message, six.text_type(e.output),
'Command "%s" fails with different message' % e.cmd) 'Command "%s" fails with different message' % e.cmd)
def resource_provider_create(self, def resource_provider_create(self,

View File

@ -14,9 +14,11 @@
# This script is executed inside post_test_hook function in devstack gate. # This script is executed inside post_test_hook function in devstack gate.
TOX_ENV=${TOX_ENV:-functional}
function generate_testr_results { function generate_testr_results {
if [ -f .testrepository/0 ]; then if [ -f .stestr/0 ]; then
.tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit .tox/$TOX_ENV/bin/stestr last --subunit > $WORKSPACE/testrepository.subunit
mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit
/usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html
gzip -9 $BASE/logs/testrepository.subunit gzip -9 $BASE/logs/testrepository.subunit
@ -37,7 +39,7 @@ echo "Running osc-placement functional test suite"
set +e set +e
# Preserve env for OS_ credentials # Preserve env for OS_ credentials
source $BASE/new/devstack/openrc admin admin source $BASE/new/devstack/openrc admin admin
tox -e ${TOX_ENV:-functional} tox -e $TOX_ENV
EXIT_CODE=$? EXIT_CODE=$?
set -e set -e

View File

@ -20,7 +20,7 @@ class TestAggregate(base.BaseTestCase):
def test_fail_if_no_rp(self): def test_fail_if_no_rp(self):
self.assertCommandFailed( self.assertCommandFailed(
'too few arguments', base.ARGUMENTS_MISSING,
self.openstack, self.openstack,
'resource provider aggregate list') 'resource provider aggregate list')
@ -50,7 +50,7 @@ class TestAggregate(base.BaseTestCase):
def test_set_aggregate_fail_if_no_rp(self): def test_set_aggregate_fail_if_no_rp(self):
self.assertCommandFailed( self.assertCommandFailed(
'too few arguments', base.ARGUMENTS_MISSING,
self.openstack, self.openstack,
'resource provider aggregate set') 'resource provider aggregate set')

View File

@ -67,7 +67,7 @@ class TestInventory(base.BaseTestCase):
# Negative test to assert command failure because # Negative test to assert command failure because
# microversion < 1.5 and --resource-class is not specified. # microversion < 1.5 and --resource-class is not specified.
self.assertCommandFailed( self.assertCommandFailed(
'argument --resource-class is required', base.ARGUMENTS_REQUIRED % '--resource-class',
self.resource_inventory_delete, self.resource_inventory_delete,
'fake_uuid') 'fake_uuid')
@ -77,7 +77,7 @@ class TestSetInventory(base.BaseTestCase):
exc = self.assertRaises( exc = self.assertRaises(
subprocess.CalledProcessError, subprocess.CalledProcessError,
self.openstack, 'resource provider inventory set') self.openstack, 'resource provider inventory set')
self.assertIn('too few arguments', exc.output.decode('utf-8')) self.assertIn(base.ARGUMENTS_MISSING, exc.output.decode('utf-8'))
def test_set_empty_inventories(self): def test_set_empty_inventories(self):
rp = self.resource_provider_create() rp = self.resource_provider_create()
@ -173,11 +173,11 @@ class TestSetInventory(base.BaseTestCase):
exc = self.assertRaises( exc = self.assertRaises(
subprocess.CalledProcessError, subprocess.CalledProcessError,
self.openstack, 'resource provider inventory class set') self.openstack, 'resource provider inventory class set')
self.assertIn('too few arguments', exc.output.decode('utf-8')) self.assertIn(base.ARGUMENTS_MISSING, exc.output.decode('utf-8'))
exc = self.assertRaises( exc = self.assertRaises(
subprocess.CalledProcessError, subprocess.CalledProcessError,
self.openstack, 'resource provider inventory class set fake_uuid') self.openstack, 'resource provider inventory class set fake_uuid')
self.assertIn('too few arguments', exc.output.decode('utf-8')) self.assertIn(base.ARGUMENTS_MISSING, exc.output.decode('utf-8'))
exc = self.assertRaises( exc = self.assertRaises(
subprocess.CalledProcessError, subprocess.CalledProcessError,
self.openstack, self.openstack,
@ -189,7 +189,7 @@ class TestSetInventory(base.BaseTestCase):
exc = self.assertRaises( exc = self.assertRaises(
subprocess.CalledProcessError, self.openstack, subprocess.CalledProcessError, self.openstack,
'resource provider inventory class set %s VCPU' % rp['uuid']) 'resource provider inventory class set %s VCPU' % rp['uuid'])
self.assertIn('argument --total is required', self.assertIn(base.ARGUMENTS_REQUIRED % '--total',
exc.output.decode('utf-8')) exc.output.decode('utf-8'))
def test_set_inventory_for_resource_class(self): def test_set_inventory_for_resource_class(self):

View File

@ -18,4 +18,4 @@ from oslotest import base
class TestPlugin(base.BaseTestCase): class TestPlugin(base.BaseTestCase):
def test_parser_options(self): def test_parser_options(self):
output = subprocess.check_output(['openstack', '--help']) output = subprocess.check_output(['openstack', '--help'])
self.assertIn('--os-placement-api-version', output) self.assertIn('--os-placement-api-version', output.decode('utf-8'))

View File

@ -10,7 +10,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import mock import mock
import six import six
@ -18,6 +17,7 @@ import six
import keystoneauth1.exceptions.http as ks_exceptions import keystoneauth1.exceptions.http as ks_exceptions
import osc_lib.exceptions as exceptions import osc_lib.exceptions as exceptions
import oslotest.base as base import oslotest.base as base
import simplejson as json
import osc_placement.http as http import osc_placement.http as http

View File

@ -47,6 +47,7 @@
export PYTHONUNBUFFERED=true export PYTHONUNBUFFERED=true
export DEVSTACK_PROJECT_FROM_GIT=osc-placement export DEVSTACK_PROJECT_FROM_GIT=osc-placement
export DEVSTACK_GATE_USE_PYTHON3=True export DEVSTACK_GATE_USE_PYTHON3=True
export TOX_ENV=functional-py3
function post_test_hook { function post_test_hook {
# Configure and run functional tests # Configure and run functional tests

View File

@ -5,4 +5,5 @@
pbr>=2.0.0 # Apache-2.0 pbr>=2.0.0 # Apache-2.0
six>=1.10.0 # MIT six>=1.10.0 # MIT
keystoneauth1>=3.3.0 # Apache-2.0 keystoneauth1>=3.3.0 # Apache-2.0
simplejson>=3.16.0 # MIT
osc-lib>=1.2.0 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0

View File

@ -5,14 +5,11 @@
hacking>=0.12.0,<0.13 # Apache-2.0 hacking>=0.12.0,<0.13 # Apache-2.0
coverage>=4.0 # Apache-2.0 coverage>=4.0 # Apache-2.0
python-subunit>=0.0.18 # Apache-2.0/BSD sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
sphinx>=1.2.1,!=1.3b1,<1.4 # BSD
oslosphinx>=4.7.0 # Apache-2.0 oslosphinx>=4.7.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0
python-openstackclient>=3.3.0 # Apache-2.0 python-openstackclient>=3.3.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD stestr>=1.0.0 # Apache-2.0
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
# releasenotes # releasenotes
reno>=1.8.0 # Apache-2.0 reno>=1.8.0 # Apache-2.0

74
tox.ini
View File

@ -1,56 +1,86 @@
[tox] [tox]
minversion = 2.0 minversion = 3.1.1
envlist = pep8,py{27,35},functional envlist = py{27,36},functional,functional-py36,pep8
skipsdist = True skipsdist = True
# Automatic envs (pyXX) will use the python version appropriate to that
# env and ignore basepython inherited from [testenv]. That's what we
# want, and we don't need to be warned about it.
ignore_basepython_conflict = True
[testenv] [testenv]
basepython = python3
usedevelop = True usedevelop = True
whitelist_externals = whitelist_externals =
rm rm
install_command = pip install {opts} {packages} install_command = pip install {opts} {packages}
setenv = setenv =
VIRTUAL_ENV={envdir} VIRTUAL_ENV={envdir}
PYTHONWARNINGS=default::DeprecationWarning PYTHONWARNINGS=ignore::DeprecationWarning:distutils,ignore::DeprecationWarning:site
OS_TEST_PATH=./osc_placement/tests/unit PYTHONDONTWRITEBYTECODE=1
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/requirements.txt
commands = python setup.py test --slowest --testr-args='{posargs}'
[testenv:functional]
setenv =
VIRTUAL_ENV={envdir}
PYTHONWARNINGS=default::DeprecationWarning,ignore::DeprecationWarning:distutils,ignore::DeprecationWarning:site
OS_TEST_PATH=./osc_placement/tests/functional
# NOTE(rpodolyaka): allow passing of keystone credentials via env variables # NOTE(rpodolyaka): allow passing of keystone credentials via env variables
passenv = OS_* passenv = OS_*
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/requirements.txt
commands = stestr run {posargs}
# NOTE(cdent): Functional tests require a running openstack
# and OS_* variables in the environment to allow authentication.
# This is because the openstack client is called against a
# real cloud.
# NOTE(cdent): Do not set envdir here, for the sake of the
# gate functional jobs, which use the 'functional' path when
# copying files.
[testenv:functional]
basepython = python2.7
commands = stestr --test-path=./osc_placement/tests/functional run {posargs}
# Used by the python3 functional job in the gate.
[testenv:functional-py3]
commands =
{[testenv:functional]commands}
[testenv:functional-py36]
envdir = {toxworkdir}/py36
commands =
{[testenv:functional]commands}
[testenv:functional-py37]
envdir = {toxworkdir}/py37
commands =
{[testenv:functional]commands}
[testenv:pep8] [testenv:pep8]
basepython = python3 envdir = {toxworkdir}/shared
commands = flake8 {posargs} commands = flake8 {posargs}
[testenv:venv] [testenv:venv]
basepython = python3
commands = {posargs} commands = {posargs}
[testenv:cover] [testenv:cover]
basepython = python3 envdir = {toxworkdir}/shared
commands = python setup.py test --coverage --testr-args='{posargs}' setenv =
{[testenv]setenv}
PYTHON=coverage run --source osc_placement --parallel-mode
commands =
coverage erase
stestr run {posargs}
coverage combine
coverage html -d cover
coverage xml -o cover/coverage.xml
coverage report
[testenv:docs] [testenv:docs]
basepython = python3
commands = commands =
rm -rf doc/build rm -rf doc/build
python setup.py build_sphinx python setup.py build_sphinx
[testenv:releasenotes] [testenv:releasenotes]
basepython = python3
commands = commands =
sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
[testenv:debug] [testenv:debug]
basepython = python3
commands = oslo_debug_helper {posargs} commands = oslo_debug_helper {posargs}
[flake8] [flake8]