Separate unit tests from fullstack tests

With this patch the tox command only runs the unit tests at
./fuxi/tests/unit folder. Another test job is added for fullstack
which is meant to be run only with a working devstack setup and
will run at the gate with tempest. You can run the tests there
for debugging with 'tox -e fullstack'

Change-Id: I2942ccfdf9a97dce331de9b27d9c485de3e112c3
Partial-Implements: blueprint fullstack-testing
This commit is contained in:
Hongbin Lu 2016-11-28 21:44:34 +00:00 committed by Hongbin Lu
parent db7497a070
commit 5b682e2333
19 changed files with 241 additions and 219 deletions

View File

@ -1,7 +1,4 @@
[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 ./ . $LISTOPT $IDOPTION
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_LOG_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./fuxi/tests/unit} $LISTOPT $IDOPTION | cat
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -0,0 +1,19 @@
# 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
class TestFuxi(base.BaseTestCase):
def test_something(self):
pass

View File

@ -1,120 +1,120 @@
# 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 fuxi.common import mount
from fuxi import exceptions
from fuxi.tests import base
class FakeMounter(object):
def __init__(self, mountinfo=None):
self.mountinfo = "/dev/0 /path/to/0 type0 flags 0 0\n" \
"/dev/1 /path/to/1 type1 flags 0 0\n" \
"/dev/2 /path/to/2 type2 flags,1,2=3 0 0\n" \
if not mountinfo else mountinfo
def mount(self, devpath, mountpoint, fstype=None):
if not fstype:
fstype = 'ext4'
self.mountinfo += ' '.join([devpath, mountpoint, fstype,
'flags', '0', '0\n'])
def unmount(self, mountpoint):
mounts = self.read_mounts()
ori_len = len(mounts)
for m in mounts:
if m.mountpoint == mountpoint:
mounts.remove(m)
if ori_len != len(mounts):
self.mountinfo = ''.join([' '.join([m.device, m.mountpoint,
m.fstype, m.opts,
'0', '0\n'])
for m in mounts])
else:
raise exceptions.UnmountException()
def read_mounts(self, filter_device=(), filter_fstype=()):
lines = self.mountinfo.split('\n')
mounts = []
for line in lines:
if not line:
continue
tokens = line.split()
if len(tokens) < 4:
continue
if tokens[0] in filter_device or tokens[1] in filter_fstype:
continue
mounts.append(mount.MountInfo(device=tokens[0],
mountpoint=tokens[1],
fstype=tokens[2], opts=tokens[3]))
return mounts
def get_mps_by_device(self, devpath):
mps = []
mounts = self.read_mounts()
for m in mounts:
if devpath in m.device:
mps.append(m.mountpoint)
return mps
def check_already_mounted(devpath, mountpoint):
mounts = FakeMounter().read_mounts()
for m in mounts:
if m.device == devpath and m.mountpoint == mountpoint:
return True
return False
class TestMounter(base.TestCase):
def setUp(self):
super(TestMounter, self).setUp()
def test_mount(self):
fake_devpath = '/dev/3'
fake_mp = '/path/to/3'
fake_fstype = 'ext4'
fake_mounter = FakeMounter()
fake_mounter.mount(fake_devpath, fake_mp, fake_fstype)
fake_mountinfo = "/dev/0 /path/to/0 type0 flags 0 0\n" \
"/dev/1 /path/to/1 type1 flags 0 0\n" \
"/dev/2 /path/to/2 type2 flags,1,2=3 0 0\n" \
"/dev/3 /path/to/3 ext4 flags 0 0\n"
self.assertEqual(fake_mountinfo, fake_mounter.mountinfo)
def test_unmount(self):
fake_mp = '/path/to/2'
fake_mounter = FakeMounter()
fake_mounter.unmount(fake_mp)
fake_mountinfo = "/dev/0 /path/to/0 type0 flags 0 0\n" \
"/dev/1 /path/to/1 type1 flags 0 0\n"
self.assertEqual(fake_mountinfo, fake_mounter.mountinfo)
def test_read_mounts(self):
fake_mounts = [str(mount.MountInfo('/dev/0', '/path/to/0',
'type0', 'flags')),
str(mount.MountInfo('/dev/1', '/path/to/1',
'type1', 'flags')),
str(mount.MountInfo('/dev/2', '/path/to/2',
'type2', 'flags,1,2=3'))]
mounts = [str(m) for m in FakeMounter().read_mounts()]
self.assertEqual(len(fake_mounts), len(mounts))
for m in mounts:
self.assertIn(m, fake_mounts)
def test_get_mps_by_device(self):
self.assertEqual(['/path/to/0'],
FakeMounter().get_mps_by_device('/dev/0'))
def test_check_alread_mounted(self):
self.assertTrue(check_already_mounted('/dev/0', '/path/to/0'))
self.assertFalse(check_already_mounted('/dev/0', '/path/to/1'))
# 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 fuxi.common import mount
from fuxi import exceptions
from fuxi.tests.unit import base
class FakeMounter(object):
def __init__(self, mountinfo=None):
self.mountinfo = "/dev/0 /path/to/0 type0 flags 0 0\n" \
"/dev/1 /path/to/1 type1 flags 0 0\n" \
"/dev/2 /path/to/2 type2 flags,1,2=3 0 0\n" \
if not mountinfo else mountinfo
def mount(self, devpath, mountpoint, fstype=None):
if not fstype:
fstype = 'ext4'
self.mountinfo += ' '.join([devpath, mountpoint, fstype,
'flags', '0', '0\n'])
def unmount(self, mountpoint):
mounts = self.read_mounts()
ori_len = len(mounts)
for m in mounts:
if m.mountpoint == mountpoint:
mounts.remove(m)
if ori_len != len(mounts):
self.mountinfo = ''.join([' '.join([m.device, m.mountpoint,
m.fstype, m.opts,
'0', '0\n'])
for m in mounts])
else:
raise exceptions.UnmountException()
def read_mounts(self, filter_device=(), filter_fstype=()):
lines = self.mountinfo.split('\n')
mounts = []
for line in lines:
if not line:
continue
tokens = line.split()
if len(tokens) < 4:
continue
if tokens[0] in filter_device or tokens[1] in filter_fstype:
continue
mounts.append(mount.MountInfo(device=tokens[0],
mountpoint=tokens[1],
fstype=tokens[2], opts=tokens[3]))
return mounts
def get_mps_by_device(self, devpath):
mps = []
mounts = self.read_mounts()
for m in mounts:
if devpath in m.device:
mps.append(m.mountpoint)
return mps
def check_already_mounted(devpath, mountpoint):
mounts = FakeMounter().read_mounts()
for m in mounts:
if m.device == devpath and m.mountpoint == mountpoint:
return True
return False
class TestMounter(base.TestCase):
def setUp(self):
super(TestMounter, self).setUp()
def test_mount(self):
fake_devpath = '/dev/3'
fake_mp = '/path/to/3'
fake_fstype = 'ext4'
fake_mounter = FakeMounter()
fake_mounter.mount(fake_devpath, fake_mp, fake_fstype)
fake_mountinfo = "/dev/0 /path/to/0 type0 flags 0 0\n" \
"/dev/1 /path/to/1 type1 flags 0 0\n" \
"/dev/2 /path/to/2 type2 flags,1,2=3 0 0\n" \
"/dev/3 /path/to/3 ext4 flags 0 0\n"
self.assertEqual(fake_mountinfo, fake_mounter.mountinfo)
def test_unmount(self):
fake_mp = '/path/to/2'
fake_mounter = FakeMounter()
fake_mounter.unmount(fake_mp)
fake_mountinfo = "/dev/0 /path/to/0 type0 flags 0 0\n" \
"/dev/1 /path/to/1 type1 flags 0 0\n"
self.assertEqual(fake_mountinfo, fake_mounter.mountinfo)
def test_read_mounts(self):
fake_mounts = [str(mount.MountInfo('/dev/0', '/path/to/0',
'type0', 'flags')),
str(mount.MountInfo('/dev/1', '/path/to/1',
'type1', 'flags')),
str(mount.MountInfo('/dev/2', '/path/to/2',
'type2', 'flags,1,2=3'))]
mounts = [str(m) for m in FakeMounter().read_mounts()]
self.assertEqual(len(fake_mounts), len(mounts))
for m in mounts:
self.assertIn(m, fake_mounts)
def test_get_mps_by_device(self):
self.assertEqual(['/path/to/0'],
FakeMounter().get_mps_by_device('/dev/0'))
def test_check_alread_mounted(self):
self.assertTrue(check_already_mounted('/dev/0', '/path/to/0'))
self.assertFalse(check_already_mounted('/dev/0', '/path/to/1'))

View File

@ -1,81 +1,83 @@
# 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.
import mock
from cinderclient import exceptions as cinder_exception
from fuxi.common import state_monitor
from fuxi import exceptions
from fuxi.tests import base, fake_client, fake_object
class TestStateMonitor(base.TestCase):
def setUp(self):
super(TestStateMonitor, self).setUp()
def test_monitor_cinder_volume(self):
fake_cinder_client = fake_client.FakeCinderClient()
fake_cinder_volume = fake_object.FakeCinderVolume(status='available')
fake_desired_state = 'in-use'
fake_transient_states = ('in-use',)
fake_time_limit = 0
fake_state_monitor = state_monitor.StateMonitor(fake_cinder_client,
fake_cinder_volume,
fake_desired_state,
fake_transient_states,
fake_time_limit)
fake_desired_volume = fake_object.FakeCinderVolume(status='in-use')
with mock.patch.object(fake_client.FakeCinderClient.Volumes, 'get',
return_value=fake_desired_volume):
self.assertEqual(fake_desired_volume,
fake_state_monitor.monitor_cinder_volume())
def test_monitor_cinder_volume_get_failed(self):
fake_cinder_client = fake_client.FakeCinderClient()
fake_cinder_volume = fake_object.FakeCinderVolume(status='available')
with mock.patch('fuxi.tests.fake_client.FakeCinderClient.Volumes.get',
side_effect=cinder_exception.ClientException(404)):
fake_state_monitor = state_monitor.StateMonitor(fake_cinder_client,
fake_cinder_volume,
None, None, -1)
self.assertRaises(exceptions.TimeoutException,
fake_state_monitor.monitor_cinder_volume)
with mock.patch('fuxi.tests.fake_client.FakeCinderClient.Volumes.get',
side_effect=cinder_exception.ClientException(404)):
fake_state_monitor = state_monitor.StateMonitor(fake_cinder_client,
fake_cinder_volume,
None, None)
self.assertRaises(cinder_exception.ClientException,
fake_state_monitor.monitor_cinder_volume)
def test_monitor_cinder_volume_unexpected_state(self):
fake_cinder_client = fake_client.FakeCinderClient()
fake_cinder_volume = fake_object.FakeCinderVolume(status='available')
fake_desired_state = 'in-use'
fake_transient_states = ('in-use',)
fake_time_limit = 0
fake_state_monitor = state_monitor.StateMonitor(fake_cinder_client,
fake_cinder_volume,
fake_desired_state,
fake_transient_states,
fake_time_limit)
fake_desired_volume = fake_object.FakeCinderVolume(status='attaching')
with mock.patch.object(fake_client.FakeCinderClient.Volumes, 'get',
return_value=fake_desired_volume):
self.assertRaises(exceptions.UnexpectedStateException,
fake_state_monitor.monitor_cinder_volume)
# 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.
import mock
from cinderclient import exceptions as cinder_exception
from fuxi.common import state_monitor
from fuxi import exceptions
from fuxi.tests.unit import base, fake_client, fake_object
class TestStateMonitor(base.TestCase):
def setUp(self):
super(TestStateMonitor, self).setUp()
def test_monitor_cinder_volume(self):
fake_cinder_client = fake_client.FakeCinderClient()
fake_cinder_volume = fake_object.FakeCinderVolume(status='available')
fake_desired_state = 'in-use'
fake_transient_states = ('in-use',)
fake_time_limit = 0
fake_state_monitor = state_monitor.StateMonitor(fake_cinder_client,
fake_cinder_volume,
fake_desired_state,
fake_transient_states,
fake_time_limit)
fake_desired_volume = fake_object.FakeCinderVolume(status='in-use')
with mock.patch.object(fake_client.FakeCinderClient.Volumes, 'get',
return_value=fake_desired_volume):
self.assertEqual(fake_desired_volume,
fake_state_monitor.monitor_cinder_volume())
def test_monitor_cinder_volume_get_failed(self):
fake_cinder_client = fake_client.FakeCinderClient()
fake_cinder_volume = fake_object.FakeCinderVolume(status='available')
with mock.patch('fuxi.tests.unit.fake_client.FakeCinderClient.Volumes'
'.get',
side_effect=cinder_exception.ClientException(404)):
fake_state_monitor = state_monitor.StateMonitor(fake_cinder_client,
fake_cinder_volume,
None, None, -1)
self.assertRaises(exceptions.TimeoutException,
fake_state_monitor.monitor_cinder_volume)
with mock.patch('fuxi.tests.unit.fake_client.FakeCinderClient.Volumes'
'.get',
side_effect=cinder_exception.ClientException(404)):
fake_state_monitor = state_monitor.StateMonitor(fake_cinder_client,
fake_cinder_volume,
None, None)
self.assertRaises(cinder_exception.ClientException,
fake_state_monitor.monitor_cinder_volume)
def test_monitor_cinder_volume_unexpected_state(self):
fake_cinder_client = fake_client.FakeCinderClient()
fake_cinder_volume = fake_object.FakeCinderVolume(status='available')
fake_desired_state = 'in-use'
fake_transient_states = ('in-use',)
fake_time_limit = 0
fake_state_monitor = state_monitor.StateMonitor(fake_cinder_client,
fake_cinder_volume,
fake_desired_state,
fake_transient_states,
fake_time_limit)
fake_desired_volume = fake_object.FakeCinderVolume(status='attaching')
with mock.patch.object(fake_client.FakeCinderClient.Volumes, 'get',
return_value=fake_desired_volume):
self.assertRaises(exceptions.UnexpectedStateException,
fake_state_monitor.monitor_cinder_volume)

View File

@ -17,7 +17,7 @@ from fuxi.common import constants
from fuxi.common import state_monitor
from fuxi.connector.cloudconnector import openstack
from fuxi import utils
from fuxi.tests import base, fake_client, fake_object
from fuxi.tests.unit import base, fake_client, fake_object
from cinderclient import exceptions as cinder_exception
from novaclient import exceptions as nova_exception
@ -68,7 +68,7 @@ class TestCinderConnector(base.TestCase):
result = self.connector.disconnect_volume(fake_cinder_volume)
self.assertIsNone(result)
@mock.patch('fuxi.tests.fake_client.FakeCinderClient.Volumes.get',
@mock.patch('fuxi.tests.unit.fake_client.FakeCinderClient.Volumes.get',
side_effect=cinder_exception.ClientException(404))
@mock.patch.object(utils, 'execute')
@mock.patch.object(state_monitor.StateMonitor,
@ -80,7 +80,7 @@ class TestCinderConnector(base.TestCase):
self.connector.disconnect_volume,
fake_cinder_volume)
@mock.patch('fuxi.tests.fake_client.FakeNovaClient.Volumes'
@mock.patch('fuxi.tests.unit.fake_client.FakeNovaClient.Volumes'
'.delete_server_volume',
side_effect=nova_exception.ClientException(500))
@mock.patch.object(utils, 'get_instance_uuid', return_value='fake-123')

View File

@ -18,7 +18,7 @@ import sys
from fuxi.common import constants
from fuxi.connector import osbrickconnector
from fuxi.tests import base, fake_client, fake_object
from fuxi.tests.unit import base, fake_client, fake_object
from fuxi import utils
from cinderclient import exceptions as cinder_exception
@ -93,7 +93,7 @@ class TestCinderConnector(base.TestCase):
@mock.patch.object(osbrickconnector, 'brick_get_connector_properties',
mock_get_connector_properties)
@mock.patch.object(utils, 'execute')
@mock.patch('fuxi.tests.fake_client.FakeCinderClient.Volumes'
@mock.patch('fuxi.tests.unit.fake_client.FakeCinderClient.Volumes'
'.initialize_connection',
side_effect=cinder_exception.ClientException(500))
def test_disconnect_volume_no_connection_info(self, mock_execute,
@ -117,7 +117,7 @@ class TestCinderConnector(base.TestCase):
return_value={'driver_volume_type': 'fake_proto',
'data': {'path': '/dev/0'}})
@mock.patch.object(utils, 'execute')
@mock.patch('fuxi.tests.fake_client.FakeOSBrickConnector'
@mock.patch('fuxi.tests.unit.fake_client.FakeOSBrickConnector'
'.disconnect_volume',
side_effect=processutils.ProcessExecutionError())
def test_disconnect_volume_osbrick_disconnect_failed(self, mock_connector,
@ -136,7 +136,7 @@ class TestCinderConnector(base.TestCase):
self.connector.disconnect_volume,
fake_cinder_volume)
@mock.patch('fuxi.tests.fake_client.FakeCinderClient.Volumes.detach',
@mock.patch('fuxi.tests.unit.fake_client.FakeCinderClient.Volumes.detach',
side_effect=cinder_exception.ClientException(500))
@mock.patch.object(osbrickconnector, 'brick_get_connector',
return_value=fake_client.FakeOSBrickConnector())

View File

@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from fuxi.tests import fake_object
from fuxi.tests.unit import fake_object
from cinderclient import exceptions as cinder_exception

View File

@ -26,7 +26,7 @@ from fuxi import app
from fuxi.common import config
from fuxi.controllers import volume_providers_conf
from fuxi import exceptions
from fuxi.tests import base
from fuxi.tests.unit import base
from oslo_serialization import jsonutils

View File

@ -20,7 +20,7 @@ from fuxi.common import constants as consts
from fuxi.common import mount
from fuxi.common import state_monitor
from fuxi import exceptions
from fuxi.tests import base, fake_client, fake_object
from fuxi.tests.unit import base, fake_client, fake_object
from fuxi import utils
from fuxi.volumeprovider import cinder
@ -95,7 +95,7 @@ class TestCinder(base.TestCase):
@mock.patch.object(cinder.Cinder, '_get_docker_volume',
return_value=(fake_object.FakeCinderVolume(
status='unknown'), consts.UNKNOWN))
@mock.patch('fuxi.tests.fake_client.FakeCinderClient.Volumes.get',
@mock.patch('fuxi.tests.unit.fake_client.FakeCinderClient.Volumes.get',
side_effect=cinder_exception.ClientException(404))
def test_create_from_volume_id_with_volume_not_exist(self,
mocK_docker_volume,
@ -256,7 +256,7 @@ class TestCinder(base.TestCase):
@mock.patch.object(FakeCinderConnector,
'get_device_path',
mock_device_path_for_delete)
@mock.patch('fuxi.tests.fake_client.FakeCinderClient.Volumes.delete',
@mock.patch('fuxi.tests.unit.fake_client.FakeCinderClient.Volumes.delete',
side_effect=cinder_exception.ClientException(500))
def test_delete_failed(self, mock_execute, mock_delete):
fd, tmpfname = tempfile.mkstemp()
@ -311,7 +311,7 @@ class TestCinder(base.TestCase):
self.cinderprovider.list())
@mock.patch.object(cinder.Cinder, '_get_connector', mock_connector)
@mock.patch('fuxi.tests.fake_client.FakeCinderClient.Volumes.list',
@mock.patch('fuxi.tests.unit.fake_client.FakeCinderClient.Volumes.list',
side_effect=cinder_exception.ClientException(500))
def test_list_failed(self, mock_list):
self.assertRaises(cinder_exception.ClientException,

View File

@ -391,7 +391,7 @@ class Cinder(provider.Provider):
return False
else:
msg = _LE("Volume %(vol_name)s %(c_vol)s "
"state %(state) is invalid")
"state %(state)s is invalid")
LOG.error(msg, {'vol_name': docker_volume_name,
'c_vol': cinder_volume,
'state': state})

View File

@ -34,6 +34,10 @@ commands = oslo_debug_helper {posargs}
basepython = python3.4
commands = oslo_debug_helper {posargs}
[testenv:fullstack]
basepython = python2.7
setenv = OS_TEST_PATH=./fuxi/tests/fullstack
[flake8]
show-source = True
builtins = _