Merge "Adds 3 additional tests to test_flavor.py script"
diff --git a/.gitignore b/.gitignore
index b4dca86..c154603 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+AUTHORS
+ChangeLog
*.pyc
etc/tempest.conf
include/swift_objects/swift_small
@@ -6,7 +8,7 @@
*.log
*.swp
*.swo
-*.egg-info
+*.egg*
.tox
.venv
dist
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..9a22c71
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,8 @@
+Ravikumar Venkatesan <ravikumar.venkatesan@hp.com> ravikumar-venkatesan <ravikumar.venkatesan@hp.com>
+Ravikumar Venkatesan <ravikumar.venkatesan@hp.com> ravikumar venkatesan <ravikumar.venkatesan@hp.com>
+Rohit Karajgi <rohit.karajgi@nttdata.com> Rohit Karajgi <rohit.karajgi@vertex.co.in>
+Jay Pipes <jaypipes@gmail.com> Jay Pipes <jpipes@librebox.gateway.2wire.net>
+<brian.waldon@rackspace.com> <bcwaldon@gmail.com>
+Daryl Walleck <daryl.walleck@rackspace.com> dwalleck <daryl.walleck@rackspace.com>
+<jeblair@hp.com> <corvus@inaugust.com>
+<jeblair@hp.com> <james.blair@rackspace.com>
diff --git a/run_tests.sh b/run_tests.sh
index e350c13..6b7ebec 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -10,6 +10,7 @@
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
echo " -s, --smoke Only run smoke tests"
echo " -w, --whitebox Only run whitebox tests"
+ echo " -c, --nova-coverage Enable Nova coverage collection"
echo " -p, --pep8 Just run pep8"
echo " -h, --help Print this usage message"
echo " -d, --debug Debug this script -- set -o xtrace"
@@ -25,6 +26,7 @@
-s|--no-site-packages) no_site_packages=1;;
-f|--force) force=1;;
-d|--debug) set -o xtrace;;
+ -c|--nova-coverage) let nova_coverage=1;;
-p|--pep8) let just_pep8=1;;
-s|--smoke) noseargs="$noseargs --attr=type=smoke";;
-w|--whitebox) noseargs="$noseargs --attr=type=whitebox";;
@@ -42,7 +44,7 @@
no_site_packages=0
force=0
wrapper=""
-
+nova_coverage=0
export NOSE_WITH_OPENSTACK=1
export NOSE_OPENSTACK_COLOR=1
@@ -83,6 +85,16 @@
${wrapper} python tools/hacking.py ${ignore} ${srcfiles}
}
+function run_coverage_start {
+ echo "Starting nova-coverage"
+ ${wrapper} python tools/tempest_coverage.py -c start
+}
+
+function run_coverage_report {
+ echo "Generating nova-coverage report"
+ ${wrapper} python tools/tempest_coverage.py -c report
+}
+
NOSETESTS="nosetests $noseargs"
if [ $never_venv -eq 0 ]
@@ -116,7 +128,15 @@
exit
fi
-run_tests || exit
+if [ $nova_coverage -eq 1 ]; then
+ run_coverage_start
+fi
+
+run_tests
+
+if [ $nova_coverage -eq 1 ]; then
+ run_coverage_report
+fi
if [ -z "$noseargs" ]; then
run_pep8
diff --git a/tempest/services/compute/xml/images_client.py b/tempest/services/compute/xml/images_client.py
index 1e8b250..bde9e16 100644
--- a/tempest/services/compute/xml/images_client.py
+++ b/tempest/services/compute/xml/images_client.py
@@ -70,6 +70,13 @@
node.findall('{http://www.w3.org/2005/Atom}link')]
return json
+ def _parse_images(self, xml):
+ json = {'images': []}
+ images = xml.getchildren()
+ for image in images:
+ json['images'].append(self._parse_image(image))
+ return json
+
def create_image(self, server_id, name, meta=None):
"""Creates an image of the original server."""
post_body = Element('createImage', name=name)
@@ -92,7 +99,7 @@
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url, self.headers)
- body = xml_to_json(etree.fromstring(body))
+ body = self._parse_images(etree.fromstring(body))
return resp, body['images']
def list_images_with_detail(self, params=None):
@@ -104,7 +111,7 @@
url = "images/detail?" + param_list
resp, body = self.get(url, self.headers)
- body = xml_to_json(etree.fromstring(body))
+ body = self._parse_images(etree.fromstring(body))
return resp, body['images']
def get_image(self, image_id):
diff --git a/tempest/services/identity/xml/admin_client.py b/tempest/services/identity/xml/admin_client.py
index 8448ae0..60897e9 100644
--- a/tempest/services/identity/xml/admin_client.py
+++ b/tempest/services/identity/xml/admin_client.py
@@ -136,6 +136,13 @@
body = self._parse_array(etree.fromstring(body))
return resp, body
+ def get_tenant_by_name(self, tenant_name):
+ resp, tenants = self.list_tenants()
+ for tenant in tenants:
+ if tenant['name'] == tenant_name:
+ return tenant
+ raise exceptions.NotFound('No such tenant')
+
def update_tenant(self, tenant_id, **kwargs):
"""Updates a tenant."""
resp, body = self.get_tenant(tenant_id)
@@ -198,6 +205,13 @@
body = self._parse_array(etree.fromstring(body))
return resp, body
+ def get_user_by_username(self, tenant_id, username):
+ resp, users = self.list_users_for_tenant(tenant_id)
+ for user in users:
+ if user['name'] == username:
+ return user
+ raise exceptions.NotFound('No such user')
+
def create_service(self, name, type, **kwargs):
"""Create a service."""
OS_KSADM = "http://docs.openstack.org/identity/api/ext/OS-KSADM/v1.0"
diff --git a/tempest/tests/boto/test_ec2_instance_run.py b/tempest/tests/boto/test_ec2_instance_run.py
index 95ef23c..331e54c 100644
--- a/tempest/tests/boto/test_ec2_instance_run.py
+++ b/tempest/tests/boto/test_ec2_instance_run.py
@@ -121,6 +121,7 @@
self.cancelResourceCleanUp(rcuk)
@attr(type='smoke')
+ @unittest.skip("Skipped until the Bug #1098112 is resolved")
def test_run_terminate_instance(self):
# EC2 run, terminate immediately
image_ami = self.ec2_client.get_image(self.images["ami"]
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index e315d78..8044d01 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -100,7 +100,7 @@
operate in an isolated tenant container.
"""
admin_client = cls._get_identity_admin_client()
- rand_name_root = cls.__name__
+ rand_name_root = rand_name(cls.__name__)
if cls.isolated_creds:
# Main user already created. Create the alt one...
rand_name_root += '-alt'
diff --git a/tempest/tests/compute/images/test_images.py b/tempest/tests/compute/images/test_images.py
index 6ebcbbc..95678a2 100644
--- a/tempest/tests/compute/images/test_images.py
+++ b/tempest/tests/compute/images/test_images.py
@@ -57,6 +57,7 @@
name = rand_name('image')
meta = {'image_type': 'test'}
resp, body = self.client.create_image(server['id'], name, meta)
+ self.assertEqual(202, resp.status)
image_id = parse_image_id(resp['location'])
self.client.wait_for_image_resp_code(image_id, 200)
self.client.wait_for_image_status(image_id, 'ACTIVE')
diff --git a/tempest/tests/compute/servers/test_server_actions.py b/tempest/tests/compute/servers/test_server_actions.py
index f4e62b1..91f0674 100644
--- a/tempest/tests/compute/servers/test_server_actions.py
+++ b/tempest/tests/compute/servers/test_server_actions.py
@@ -44,7 +44,7 @@
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
def tearDown(self):
- self.client.delete_server(self.server_id)
+ self.clear_servers()
@attr(type='smoke')
@unittest.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
@@ -67,11 +67,9 @@
# The server should be power cycled
if self.run_ssh:
# Get the time the server was last rebooted,
- # waiting for one minute as who doesn't have seconds precision
resp, server = self.client.get_server(self.server_id)
linux_client = RemoteClient(server, self.ssh_user, self.password)
boot_time = linux_client.get_boot_time()
- time.sleep(60)
resp, body = self.client.reboot(self.server_id, 'HARD')
self.assertEqual(202, resp.status)
@@ -89,11 +87,9 @@
# The server should be signaled to reboot gracefully
if self.run_ssh:
# Get the time the server was last rebooted,
- # waiting for one minute as who doesn't have seconds precision
resp, server = self.client.get_server(self.server_id)
linux_client = RemoteClient(server, self.ssh_user, self.password)
boot_time = linux_client.get_boot_time()
- time.sleep(60)
resp, body = self.client.reboot(self.server_id, 'SOFT')
self.assertEqual(202, resp.status)
diff --git a/tempest/tests/identity/admin/test_tenants.py b/tempest/tests/identity/admin/test_tenants.py
index 8fba7e3..578af4a 100644
--- a/tempest/tests/identity/admin/test_tenants.py
+++ b/tempest/tests/identity/admin/test_tenants.py
@@ -17,6 +17,7 @@
import unittest2 as unittest
+from nose.plugins.attrib import attr
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.tests.identity import base
@@ -24,21 +25,6 @@
class TenantsTestBase(object):
- @staticmethod
- def setUpClass(cls):
- for _ in xrange(5):
- resp, tenant = cls.client.create_tenant(rand_name('tenant-'))
- cls.data.tenants.append(tenant)
-
- def test_list_tenants(self):
- # Return a list of all tenants
- resp, body = self.client.list_tenants()
- found = [tenant for tenant in body if tenant in self.data.tenants]
- self.assertTrue(any(found), 'List did not return newly created '
- 'tenants')
- self.assertEqual(len(found), len(self.data.tenants))
- self.assertTrue(resp['status'].startswith('2'))
-
def test_list_tenants_by_unauthorized_user(self):
# Non-admin user should not be able to list tenants
self.assertRaises(exceptions.Unauthorized,
@@ -51,41 +37,50 @@
self.assertRaises(exceptions.Unauthorized, self.client.list_tenants)
self.client.clear_auth()
- def test_tenant_delete(self):
+ def test_tenant_list_delete(self):
# Create several tenants and delete them
tenants = []
- for _ in xrange(5):
- resp, body = self.client.create_tenant(rand_name('tenant-new'))
- tenants.append(body['id'])
-
+ for _ in xrange(3):
+ resp, tenant = self.client.create_tenant(rand_name('tenant-new'))
+ self.data.tenants.append(tenant)
+ tenants.append(tenant)
+ tenant_ids = map(lambda x: x['id'], tenants)
resp, body = self.client.list_tenants()
- found_1 = [tenant for tenant in body if tenant['id'] in tenants]
- for tenant_id in tenants:
- resp, body = self.client.delete_tenant(tenant_id)
+ self.assertTrue(resp['status'].startswith('2'))
+ found = [tenant for tenant in body if tenant['id'] in tenant_ids]
+ self.assertEqual(len(found), len(tenants), 'Tenants not created')
+
+ for tenant in tenants:
+ resp, body = self.client.delete_tenant(tenant['id'])
self.assertTrue(resp['status'].startswith('2'))
+ self.data.tenants.remove(tenant)
resp, body = self.client.list_tenants()
- found_2 = [tenant for tenant in body if tenant['id'] in tenants]
- self.assertTrue(any(found_1), 'Tenants not created')
- self.assertFalse(any(found_2), 'Tenants failed to delete')
+ found = [tenant for tenant in body if tenant['id'] in tenant_ids]
+ self.assertFalse(any(found), 'Tenants failed to delete')
+ @attr(type='negative')
def test_tenant_delete_by_unauthorized_user(self):
# Non-admin user should not be able to delete a tenant
tenant_name = rand_name('tenant-')
resp, tenant = self.client.create_tenant(tenant_name)
+ self.data.tenants.append(tenant)
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.delete_tenant, tenant['id'])
+ @attr(type='negative')
def test_tenant_delete_request_without_token(self):
# Request to delete a tenant without a valid token should fail
tenant_name = rand_name('tenant-')
resp, tenant = self.client.create_tenant(tenant_name)
+ self.data.tenants.append(tenant)
token = self.client.get_auth()
self.client.delete_token(token)
self.assertRaises(exceptions.Unauthorized, self.client.delete_tenant,
tenant['id'])
self.client.clear_auth()
+ @attr(type='negative')
def test_delete_non_existent_tenant(self):
# Attempt to delete a non existent tenant should fail
self.assertRaises(exceptions.NotFound, self.client.delete_tenant,
@@ -97,6 +92,8 @@
tenant_desc = rand_name('desc-')
resp, body = self.client.create_tenant(tenant_name,
description=tenant_desc)
+ tenant = body
+ self.data.tenants.append(tenant)
st1 = resp['status']
tenant_id = body['id']
desc1 = body['description']
@@ -108,11 +105,14 @@
self.assertEqual(desc2, tenant_desc, 'Description does not appear'
'to be set')
self.client.delete_tenant(tenant_id)
+ self.data.tenants.remove(tenant)
def test_tenant_create_enabled(self):
# Create a tenant that is enabled
tenant_name = rand_name('tenant-')
resp, body = self.client.create_tenant(tenant_name, enabled=True)
+ tenant = body
+ self.data.tenants.append(tenant)
tenant_id = body['id']
st1 = resp['status']
en1 = body['enabled']
@@ -122,11 +122,14 @@
en2 = body['enabled']
self.assertTrue(en2, 'Enable should be True in lookup')
self.client.delete_tenant(tenant_id)
+ self.data.tenants.remove(tenant)
def test_tenant_create_not_enabled(self):
# Create a tenant that is not enabled
tenant_name = rand_name('tenant-')
resp, body = self.client.create_tenant(tenant_name, enabled=False)
+ tenant = body
+ self.data.tenants.append(tenant)
tenant_id = body['id']
st1 = resp['status']
en1 = body['enabled']
@@ -138,11 +141,15 @@
self.assertEqual('false', str(en2).lower(),
'Enable should be False in lookup')
self.client.delete_tenant(tenant_id)
+ self.data.tenants.remove(tenant)
+ @attr(type='negative')
def test_tenant_create_duplicate(self):
# Tenant names should be unique
tenant_name = rand_name('tenant-dup-')
resp, body = self.client.create_tenant(tenant_name)
+ tenant = body
+ self.data.tenants.append(tenant)
tenant1_id = body.get('id')
try:
@@ -151,15 +158,17 @@
self.fail('Should not be able to create a duplicate tenant name')
except exceptions.Duplicate:
pass
- if tenant1_id:
- self.client.delete_tenant(tenant1_id)
+ self.client.delete_tenant(tenant1_id)
+ self.data.tenants.remove(tenant)
+ @attr(type='negative')
def test_create_tenant_by_unauthorized_user(self):
# Non-admin user should not be authorized to create a tenant
tenant_name = rand_name('tenant-')
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.create_tenant, tenant_name)
+ @attr(type='negative')
def test_create_tenant_request_without_token(self):
# Create tenant request without a token should not be authorized
tenant_name = rand_name('tenant-')
@@ -169,6 +178,7 @@
tenant_name)
self.client.clear_auth()
+ @attr(type='negative')
def test_create_tenant_with_empty_name(self):
# Tenant name should not be empty
self.assertRaises(exceptions.BadRequest, self.client.create_tenant,
@@ -184,6 +194,9 @@
# Update name attribute of a tenant
t_name1 = rand_name('tenant-')
resp, body = self.client.create_tenant(t_name1)
+ tenant = body
+ self.data.tenants.append(tenant)
+
t_id = body['id']
resp1_name = body['name']
@@ -202,12 +215,16 @@
self.assertEqual(resp2_name, resp3_name)
self.client.delete_tenant(t_id)
+ self.data.tenants.remove(tenant)
def test_tenant_update_desc(self):
# Update description attribute of a tenant
t_name = rand_name('tenant-')
t_desc = rand_name('desc-')
resp, body = self.client.create_tenant(t_name, description=t_desc)
+ tenant = body
+ self.data.tenants.append(tenant)
+
t_id = body['id']
resp1_desc = body['description']
@@ -226,12 +243,16 @@
self.assertEqual(resp2_desc, resp3_desc)
self.client.delete_tenant(t_id)
+ self.data.tenants.remove(tenant)
def test_tenant_update_enable(self):
# Update the enabled attribute of a tenant
t_name = rand_name('tenant-')
t_en = False
resp, body = self.client.create_tenant(t_name, enabled=t_en)
+ tenant = body
+ self.data.tenants.append(tenant)
+
t_id = body['id']
resp1_en = body['enabled']
@@ -250,6 +271,7 @@
self.assertEqual(resp2_en, resp3_en)
self.client.delete_tenant(t_id)
+ self.data.tenants.remove(tenant)
class TenantsTestJSON(base.BaseIdentityAdminTestJSON,
@@ -258,7 +280,6 @@
@classmethod
def setUpClass(cls):
super(TenantsTestJSON, cls).setUpClass()
- TenantsTestBase.setUpClass(cls)
class TenantsTestXML(base.BaseIdentityAdminTestXML, TenantsTestBase):
@@ -266,4 +287,3 @@
@classmethod
def setUpClass(cls):
super(TenantsTestXML, cls).setUpClass()
- TenantsTestBase.setUpClass(cls)
diff --git a/tempest/tests/volume/admin/test_volume_types.py b/tempest/tests/volume/admin/test_volume_types.py
index 8ebb78f..65c975a 100644
--- a/tempest/tests/volume/admin/test_volume_types.py
+++ b/tempest/tests/volume/admin/test_volume_types.py
@@ -126,7 +126,8 @@
try:
body = {}
name = rand_name("volume-type-")
- extra_specs = {"Spec1": "Val1", "Spec2": "Val2"}
+ extra_specs = {"storage_protocol": "iSCSI",
+ "vendor_name": "Open Source"}
resp, body = self.client.\
create_volume_type(name, extra_specs=extra_specs)
self.assertEqual(200, resp.status)
diff --git a/tempest/tests/volume/test_volumes_actions.py b/tempest/tests/volume/test_volumes_actions.py
index 7eddb67..155acb6 100644
--- a/tempest/tests/volume/test_volumes_actions.py
+++ b/tempest/tests/volume/test_volumes_actions.py
@@ -46,7 +46,10 @@
super(VolumesActionsTest, cls).tearDownClass()
# Delete the test instance and volume
cls.client.delete_volume(cls.volume['id'])
+ cls.client.wait_for_resource_deletion(cls.volume['id'])
+
cls.servers_client.delete_server(cls.server['id'])
+ cls.client.wait_for_resource_deletion(cls.server['id'])
@attr(type='smoke')
def test_attach_detach_volume_to_instance(self):
diff --git a/tempest/tests/volume/test_volumes_list.py b/tempest/tests/volume/test_volumes_list.py
index 26a85b7..2fc1353 100644
--- a/tempest/tests/volume/test_volumes_list.py
+++ b/tempest/tests/volume/test_volumes_list.py
@@ -87,8 +87,9 @@
# because the backing file size of the volume group is
# too small. So, here, we clean up whatever we did manage
# to create and raise a SkipTest
- for volume in cls.volume_id_list:
- cls.client.delete_volume(volume)
+ for volid in cls.volume_id_list:
+ cls.client.delete_volume(volid)
+ cls.client.wait_for_resource_deletion(volid)
msg = ("Failed to create ALL necessary volumes to run "
"test. This typically means that the backing file "
"size of the nova-volumes group is too small to "
@@ -99,9 +100,9 @@
@classmethod
def tearDownClass(cls):
# Delete the created volumes
- for volume in cls.volume_id_list:
- resp, _ = cls.client.delete_volume(volume)
- cls.client.wait_for_resource_deletion(volume)
+ for volid in cls.volume_id_list:
+ resp, _ = cls.client.delete_volume(volid)
+ cls.client.wait_for_resource_deletion(volid)
super(VolumeListTestXML, cls).tearDownClass()
@@ -133,8 +134,9 @@
# because the backing file size of the volume group is
# too small. So, here, we clean up whatever we did manage
# to create and raise a SkipTest
- for volume in cls.volume_id_list:
- cls.client.delete_volume(volume)
+ for volid in cls.volume_id_list:
+ cls.client.delete_volume(volid)
+ cls.client.wait_for_resource_deletion(volid)
msg = ("Failed to create ALL necessary volumes to run "
"test. This typically means that the backing file "
"size of the nova-volumes group is too small to "
@@ -145,7 +147,7 @@
@classmethod
def tearDownClass(cls):
# Delete the created volumes
- for volume in cls.volume_id_list:
- resp, _ = cls.client.delete_volume(volume)
- cls.client.wait_for_resource_deletion(volume)
+ for volid in cls.volume_id_list:
+ resp, _ = cls.client.delete_volume(volid)
+ cls.client.wait_for_resource_deletion(volid)
super(VolumeListTestJSON, cls).tearDownClass()
diff --git a/tools/tempest_coverage.py b/tools/tempest_coverage.py
new file mode 100755
index 0000000..73dcfbc
--- /dev/null
+++ b/tools/tempest_coverage.py
@@ -0,0 +1,194 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM
+#
+# 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 json
+import os
+import re
+import shutil
+import sys
+
+from tempest.common.rest_client import RestClient
+from tempest import config
+from tempest.openstack.common import cfg
+from tempest.tests.compute import base
+
+CONF = config.TempestConfig()
+
+
+class CoverageClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(CoverageClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def start_coverage(self):
+ post_body = {
+ 'start': {},
+ }
+ post_body = json.dumps(post_body)
+ return self.post('os-coverage/action', post_body, self.headers)
+
+ def start_coverage_combine(self):
+ post_body = {
+ 'start': {
+ 'combine': True,
+ },
+ }
+ post_body = json.dumps(post_body)
+ return self.post('os-coverage/action', post_body, self.headers)
+
+ def stop_coverage(self):
+ post_body = {
+ 'stop': {},
+ }
+ post_body = json.dumps(post_body)
+ resp, body = self.post('os-coverage/action', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def report_coverage_xml(self, file=None):
+ post_body = {
+ 'report': {
+ 'file': 'coverage.report',
+ 'xml': True,
+ },
+ }
+ if file:
+ post_body['report']['file'] = file
+ post_body = json.dumps(post_body)
+ resp, body = self.post('os-coverage/action', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def report_coverage(self, file=None):
+ post_body = {
+ 'report': {
+ 'file': 'coverage.report',
+ },
+ }
+ if file:
+ post_body['report']['file'] = file
+ post_body = json.dumps(post_body)
+ resp, body = self.post('os-coverage/action', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def report_coverage_html(self, file=None):
+ post_body = {
+ 'report': {
+ 'file': 'coverage.report',
+ 'html': True,
+ },
+ }
+ if file:
+ post_body['report']['file'] = file
+ post_body = json.dumps(post_body)
+ resp, body = self.post('os-coverage/action', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+
+def parse_opts(argv):
+ cli_opts = [
+ cfg.StrOpt('command',
+ short='c',
+ default='',
+ help="This required argument is used to specify the "
+ "coverage command to run. Only 'start', "
+ "'stop', or 'report' are valid fields."),
+ cfg.StrOpt('filename',
+ default='tempest-coverage',
+ help="Specify a filename to be used for generated report "
+ "files"),
+ cfg.BoolOpt('xml',
+ default=False,
+ help='Generate XML reports instead of text'),
+ cfg.BoolOpt('html',
+ default=False,
+ help='Generate HTML reports instead of text'),
+ cfg.BoolOpt('combine',
+ default=False,
+ help='Generate a single report for all services'),
+ cfg.StrOpt('output',
+ short='o',
+ default=None,
+ help='Optional directory to copy generated coverage data or'
+ ' reports into. This directory must not already exist '
+ 'it will be created')
+ ]
+ CLI = cfg.ConfigOpts()
+ CLI.register_cli_opts(cli_opts)
+ CLI(argv[1:])
+ return CLI
+
+
+def main(argv):
+ CLI = parse_opts(argv)
+ client_args = (CONF, CONF.compute_admin.username,
+ CONF.compute_admin.password, CONF.identity.auth_url,
+ CONF.compute_admin.tenant_name)
+ coverage_client = CoverageClientJSON(*client_args)
+
+ if CLI.command == 'start':
+ if CLI.combine:
+ coverage_client.start_coverage_combine()
+ else:
+ coverage_client.start_coverage()
+
+ elif CLI.command == 'stop':
+ resp, body = coverage_client.stop_coverage()
+ if not resp['status'] == '200':
+ print 'coverage stop failed with: %s:' % (resp['status'] + ': '
+ + body)
+ exit(int(resp['status']))
+ path = body['path']
+ if CLI.output:
+ shutil.copytree(path, CLI.output)
+ else:
+ print "Data files located at: %s" % path
+
+ elif CLI.command == 'report':
+ if CLI.xml:
+ resp, body = coverage_client.report_coverage_xml(file=CLI.filename)
+ elif CLI.html:
+ resp, body = coverage_client.report_coverage_html(
+ file=CLI.filename)
+ else:
+ resp, body = coverage_client.report_coverage(file=CLI.filename)
+ if not resp['status'] == '200':
+ print 'coverage report failed with: %s:' % (resp['status'] + ': '
+ + body)
+ exit(int(resp['status']))
+ path = body['path']
+ if CLI.output:
+ if CLI.html:
+ shutil.copytree(path, CLI.output)
+ else:
+ path = os.path.dirname(path)
+ shutil.copytree(path, CLI.output)
+ else:
+ if not CLI.html:
+ path = os.path.dirname(path)
+ print 'Report files located at: %s' % path
+
+ else:
+ print 'Invalid command'
+ exit(1)
+
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/tox.ini b/tox.ini
index da1672b..33ca1c4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,6 +13,11 @@
-r{toxinidir}/tools/test-requires
commands = nosetests {posargs}
+[testenv:coverage]
+commands = python tools/tempest_coverage.py -c start --combine
+ nosetests {posargs}
+ python tools/tempest_coverage.py -c report --html
+
[testenv:pep8]
deps = pep8==1.3.3
commands = python tools/hacking.py --ignore=E122,E125,E126 --repeat --show-source --exclude=.venv,.tox,dist,doc,openstack,*egg .