Merge "Increase the 'all' timeout because slow tests are included"
diff --git a/HACKING.rst b/HACKING.rst
index 83d67a9..fd63d64 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -260,15 +260,15 @@
example of this would be::
class TestVolumeBootPattern(manager.ScenarioTest):
- """
- This test case attempts to reproduce the following steps:
+ """
+ This test case attempts to reproduce the following steps:
- * Create in Cinder some bootable volume importing a Glance image
- * Boot an instance from the bootable volume
- * Write content to the volume
- * Delete an instance and Boot a new instance from the volume
- * Check written content in the instance
- * Create a volume snapshot while the instance is running
- * Boot an additional instance from the new snapshot based volume
- * Check written content in the instance booted from snapshot
- """
+ * Create in Cinder some bootable volume importing a Glance image
+ * Boot an instance from the bootable volume
+ * Write content to the volume
+ * Delete an instance and Boot a new instance from the volume
+ * Check written content in the instance
+ * Create a volume snapshot while the instance is running
+ * Boot an additional instance from the new snapshot based volume
+ * Check written content in the instance booted from snapshot
+ """
diff --git a/requirements.txt b/requirements.txt
index 708ede3..ac72017 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -27,3 +27,4 @@
iso8601>=0.1.9
fixtures>=0.3.14
testscenarios>=0.4
+tempest-lib
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index d365f3a..3307159 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -296,7 +296,7 @@
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
new_flavor_id = data_utils.rand_int_id(start=1000)
- ram = " 1024 "
+ ram = "1024"
resp, flavor = self.client.create_flavor(flavor_name,
ram, self.vcpus,
self.disk,
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 6507ce1..2f53a0b 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -72,6 +72,8 @@
cls.quotas_client = cls.os.quotas_client
# NOTE(mriedem): os-quota-class-sets is v2 API only
cls.quota_classes_client = cls.os.quota_classes_client
+ # NOTE(mriedem): os-networks is v2 API only
+ cls.networks_client = cls.os.networks_client
cls.limits_client = cls.os.limits_client
cls.volumes_extensions_client = cls.os.volumes_extensions_client
cls.volumes_client = cls.os.volumes_client
diff --git a/tempest/api/compute/test_networks.py b/tempest/api/compute/test_networks.py
new file mode 100644
index 0000000..86779b3
--- /dev/null
+++ b/tempest/api/compute/test_networks.py
@@ -0,0 +1,33 @@
+# Copyright 2014 IBM Corp.
+#
+# 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 tempest.api.compute import base
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class NetworksTestJSON(base.BaseV2ComputeTest):
+ @classmethod
+ def resource_setup(cls):
+ if CONF.service_available.neutron:
+ raise cls.skipException('nova-network is not available.')
+ super(NetworksTestJSON, cls).resource_setup()
+ cls.client = cls.os.networks_client
+
+ @test.attr(type='gate')
+ def test_list_networks(self):
+ _, networks = self.client.list_networks()
+ self.assertNotEmpty(networks, "No networks found.")
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index ca6d7fe..8dd2df2 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -14,46 +14,20 @@
# under the License.
import functools
-import os
-import shlex
-import subprocess
+from tempest_lib.cli import base
import testtools
-import tempest.cli.output_parser
+from tempest.common import credentials
from tempest import config
from tempest import exceptions
-from tempest.openstack.common import log as logging
from tempest.openstack.common import versionutils
-import tempest.test
+from tempest import test
-LOG = logging.getLogger(__name__)
-
CONF = config.CONF
-def execute(cmd, action, flags='', params='', fail_ok=False,
- merge_stderr=False):
- """Executes specified command for the given action."""
- cmd = ' '.join([os.path.join(CONF.cli.cli_dir, cmd),
- flags, action, params])
- LOG.info("running: '%s'" % cmd)
- cmd = shlex.split(cmd.encode('utf-8'))
- result = ''
- result_err = ''
- stdout = subprocess.PIPE
- stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
- proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
- result, result_err = proc.communicate()
- if not fail_ok and proc.returncode != 0:
- raise exceptions.CommandFailed(proc.returncode,
- cmd,
- result,
- result_err)
- return result
-
-
def check_client_version(client, version):
"""Checks if the client's version is compatible with the given version
@@ -62,8 +36,8 @@
@return: True if the client version is compatible with the given version
parameter, False otherwise.
"""
- current_version = execute(client, '', params='--version',
- merge_stderr=True)
+ current_version = base.execute(client, '', params='--version',
+ merge_stderr=True, cli_dir=CONF.cli.cli_dir)
if not current_version.strip():
raise exceptions.TempestException('"%s --version" output was empty' %
@@ -92,100 +66,19 @@
return decorator
-class ClientTestBase(tempest.test.BaseTestCase):
+class ClientTestBase(base.ClientTestBase, test.BaseTestCase):
@classmethod
def resource_setup(cls):
if not CONF.cli.enabled:
msg = "cli testing disabled"
raise cls.skipException(msg)
super(ClientTestBase, cls).resource_setup()
+ cls.cred_prov = credentials.get_isolated_credentials(cls.__name__)
+ cls.creds = cls.cred_prov.get_admin_creds()
- def __init__(self, *args, **kwargs):
- self.parser = tempest.cli.output_parser
- super(ClientTestBase, self).__init__(*args, **kwargs)
-
- def nova(self, action, flags='', params='', admin=True, fail_ok=False):
- """Executes nova command for the given action."""
- flags += ' --endpoint-type %s' % CONF.compute.endpoint_type
- return self.cmd_with_auth(
- 'nova', action, flags, params, admin, fail_ok)
-
- def nova_manage(self, action, flags='', params='', fail_ok=False,
- merge_stderr=False):
- """Executes nova-manage command for the given action."""
- return execute(
- 'nova-manage', action, flags, params, fail_ok, merge_stderr)
-
- def keystone(self, action, flags='', params='', admin=True, fail_ok=False):
- """Executes keystone command for the given action."""
- return self.cmd_with_auth(
- 'keystone', action, flags, params, admin, fail_ok)
-
- def glance(self, action, flags='', params='', admin=True, fail_ok=False):
- """Executes glance command for the given action."""
- flags += ' --os-endpoint-type %s' % CONF.image.endpoint_type
- return self.cmd_with_auth(
- 'glance', action, flags, params, admin, fail_ok)
-
- def ceilometer(self, action, flags='', params='', admin=True,
- fail_ok=False):
- """Executes ceilometer command for the given action."""
- flags += ' --os-endpoint-type %s' % CONF.telemetry.endpoint_type
- return self.cmd_with_auth(
- 'ceilometer', action, flags, params, admin, fail_ok)
-
- def heat(self, action, flags='', params='', admin=True,
- fail_ok=False):
- """Executes heat command for the given action."""
- flags += ' --os-endpoint-type %s' % CONF.orchestration.endpoint_type
- return self.cmd_with_auth(
- 'heat', action, flags, params, admin, fail_ok)
-
- def cinder(self, action, flags='', params='', admin=True, fail_ok=False):
- """Executes cinder command for the given action."""
- flags += ' --endpoint-type %s' % CONF.volume.endpoint_type
- return self.cmd_with_auth(
- 'cinder', action, flags, params, admin, fail_ok)
-
- def swift(self, action, flags='', params='', admin=True, fail_ok=False):
- """Executes swift command for the given action."""
- flags += ' --os-endpoint-type %s' % CONF.object_storage.endpoint_type
- return self.cmd_with_auth(
- 'swift', action, flags, params, admin, fail_ok)
-
- def neutron(self, action, flags='', params='', admin=True, fail_ok=False):
- """Executes neutron command for the given action."""
- flags += ' --endpoint-type %s' % CONF.network.endpoint_type
- return self.cmd_with_auth(
- 'neutron', action, flags, params, admin, fail_ok)
-
- def sahara(self, action, flags='', params='', admin=True,
- fail_ok=False, merge_stderr=True):
- """Executes sahara command for the given action."""
- flags += ' --endpoint-type %s' % CONF.data_processing.endpoint_type
- return self.cmd_with_auth(
- 'sahara', action, flags, params, admin, fail_ok, merge_stderr)
-
- def cmd_with_auth(self, cmd, action, flags='', params='',
- admin=True, fail_ok=False, merge_stderr=False):
- """Executes given command with auth attributes appended."""
- # TODO(jogo) make admin=False work
- creds = ('--os-username %s --os-tenant-name %s --os-password %s '
- '--os-auth-url %s' %
- (CONF.identity.admin_username,
- CONF.identity.admin_tenant_name,
- CONF.identity.admin_password,
- CONF.identity.uri))
- flags = creds + ' ' + flags
- return execute(cmd, action, flags, params, fail_ok, merge_stderr)
-
- def assertTableStruct(self, items, field_names):
- """Verify that all items has keys listed in field_names."""
- for item in items:
- for field in field_names:
- self.assertIn(field, item)
-
- def assertFirstLineStartsWith(self, lines, beginning):
- self.assertTrue(lines[0].startswith(beginning),
- msg=('Beginning of first line has invalid content: %s'
- % lines[:3]))
+ def _get_clients(self):
+ clients = base.CLIClient(self.creds.username,
+ self.creds.password,
+ self.creds.tenant_name,
+ CONF.identity.uri, CONF.cli.cli_dir)
+ return clients
diff --git a/tempest/cli/output_parser.py b/tempest/cli/output_parser.py
deleted file mode 100644
index 80234a3..0000000
--- a/tempest/cli/output_parser.py
+++ /dev/null
@@ -1,171 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-# All Rights Reserved.
-#
-# 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.
-
-"""Collection of utilities for parsing CLI clients output."""
-
-import re
-
-from tempest import exceptions
-from tempest.openstack.common import log as logging
-
-
-LOG = logging.getLogger(__name__)
-
-
-delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
-
-
-def details_multiple(output_lines, with_label=False):
- """Return list of dicts with item details from cli output tables.
-
- If with_label is True, key '__label' is added to each items dict.
- For more about 'label' see OutputParser.tables().
- """
- items = []
- tables_ = tables(output_lines)
- for table_ in tables_:
- if 'Property' not in table_['headers'] \
- or 'Value' not in table_['headers']:
- raise exceptions.InvalidStructure()
- item = {}
- for value in table_['values']:
- item[value[0]] = value[1]
- if with_label:
- item['__label'] = table_['label']
- items.append(item)
- return items
-
-
-def details(output_lines, with_label=False):
- """Return dict with details of first item (table) found in output."""
- items = details_multiple(output_lines, with_label)
- return items[0]
-
-
-def listing(output_lines):
- """Return list of dicts with basic item info parsed from cli output.
- """
-
- items = []
- table_ = table(output_lines)
- for row in table_['values']:
- item = {}
- for col_idx, col_key in enumerate(table_['headers']):
- item[col_key] = row[col_idx]
- items.append(item)
- return items
-
-
-def tables(output_lines):
- """Find all ascii-tables in output and parse them.
-
- Return list of tables parsed from cli output as dicts.
- (see OutputParser.table())
-
- And, if found, label key (separated line preceding the table)
- is added to each tables dict.
- """
- tables_ = []
-
- table_ = []
- label = None
-
- start = False
- header = False
-
- if not isinstance(output_lines, list):
- output_lines = output_lines.split('\n')
-
- for line in output_lines:
- if delimiter_line.match(line):
- if not start:
- start = True
- elif not header:
- # we are after head area
- header = True
- else:
- # table ends here
- start = header = None
- table_.append(line)
-
- parsed = table(table_)
- parsed['label'] = label
- tables_.append(parsed)
-
- table_ = []
- label = None
- continue
- if start:
- table_.append(line)
- else:
- if label is None:
- label = line
- else:
- LOG.warn('Invalid line between tables: %s' % line)
- if len(table_) > 0:
- LOG.warn('Missing end of table')
-
- return tables_
-
-
-def table(output_lines):
- """Parse single table from cli output.
-
- Return dict with list of column names in 'headers' key and
- rows in 'values' key.
- """
- table_ = {'headers': [], 'values': []}
- columns = None
-
- if not isinstance(output_lines, list):
- output_lines = output_lines.split('\n')
-
- if not output_lines[-1]:
- # skip last line if empty (just newline at the end)
- output_lines = output_lines[:-1]
-
- for line in output_lines:
- if delimiter_line.match(line):
- columns = _table_columns(line)
- continue
- if '|' not in line:
- LOG.warn('skipping invalid table line: %s' % line)
- continue
- row = []
- for col in columns:
- row.append(line[col[0]:col[1]].strip())
- if table_['headers']:
- table_['values'].append(row)
- else:
- table_['headers'] = row
-
- return table_
-
-
-def _table_columns(first_table_row):
- """Find column ranges in output line.
-
- Return list of tuples (start,end) for each column
- detected by plus (+) characters in delimiter line.
- """
- positions = []
- start = 1 # there is '+' at 0
- while start < len(first_table_row):
- end = first_table_row.find('+', start)
- if end == -1:
- break
- positions.append((start, end))
- start = end + 1
- return positions
diff --git a/tempest/cli/simple_read_only/compute/test_nova.py b/tempest/cli/simple_read_only/compute/test_nova.py
index 6e5e077..4fe4982 100644
--- a/tempest/cli/simple_read_only/compute/test_nova.py
+++ b/tempest/cli/simple_read_only/compute/test_nova.py
@@ -13,11 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest_lib import exceptions
import testtools
from tempest import cli
from tempest import config
-from tempest import exceptions
from tempest.openstack.common import log as logging
import tempest.test
@@ -47,6 +47,11 @@
raise cls.skipException(msg)
super(SimpleReadOnlyNovaClientTest, cls).resource_setup()
+ def nova(self, *args, **kwargs):
+ return self.clients.nova(*args,
+ endpoint_type=CONF.compute.endpoint_type,
+ **kwargs)
+
def test_admin_fake_action(self):
self.assertRaises(exceptions.CommandFailed,
self.nova,
diff --git a/tempest/cli/simple_read_only/compute/test_nova_manage.py b/tempest/cli/simple_read_only/compute/test_nova_manage.py
index cff543f..34ec671 100644
--- a/tempest/cli/simple_read_only/compute/test_nova_manage.py
+++ b/tempest/cli/simple_read_only/compute/test_nova_manage.py
@@ -13,9 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest_lib import exceptions
+
from tempest import cli
from tempest import config
-from tempest import exceptions
from tempest.openstack.common import log as logging
@@ -46,6 +47,9 @@
raise cls.skipException(msg)
super(SimpleReadOnlyNovaManageTest, cls).resource_setup()
+ def nova_manage(self, *args, **kwargs):
+ return self.clients.nova_manage(*args, **kwargs)
+
def test_admin_fake_action(self):
self.assertRaises(exceptions.CommandFailed,
self.nova_manage,
diff --git a/tempest/cli/simple_read_only/data_processing/test_sahara.py b/tempest/cli/simple_read_only/data_processing/test_sahara.py
index 751a4ad..1f2403c 100644
--- a/tempest/cli/simple_read_only/data_processing/test_sahara.py
+++ b/tempest/cli/simple_read_only/data_processing/test_sahara.py
@@ -15,9 +15,10 @@
import logging
import re
+from tempest_lib import exceptions
+
from tempest import cli
from tempest import config
-from tempest import exceptions
from tempest import test
CONF = config.CONF
@@ -40,6 +41,10 @@
raise cls.skipException(msg)
super(SimpleReadOnlySaharaClientTest, cls).resource_setup()
+ def sahara(self, *args, **kwargs):
+ return self.clients.sahara(
+ *args, endpoint_type=CONF.data_processing.endpoint_type, **kwargs)
+
@test.attr(type='negative')
def test_sahara_fake_action(self):
self.assertRaises(exceptions.CommandFailed,
diff --git a/tempest/cli/simple_read_only/identity/test_keystone.py b/tempest/cli/simple_read_only/identity/test_keystone.py
index 9218fcd..1fc7908 100644
--- a/tempest/cli/simple_read_only/identity/test_keystone.py
+++ b/tempest/cli/simple_read_only/identity/test_keystone.py
@@ -15,9 +15,10 @@
import re
+from tempest_lib import exceptions
+
from tempest import cli
from tempest import config
-from tempest import exceptions
from tempest.openstack.common import log as logging
CONF = config.CONF
@@ -34,6 +35,9 @@
their own. They only verify the structure of output if present.
"""
+ def keystone(self, *args, **kwargs):
+ return self.clients.keystone(*args, **kwargs)
+
def test_admin_fake_action(self):
self.assertRaises(exceptions.CommandFailed,
self.keystone,
diff --git a/tempest/cli/simple_read_only/image/test_glance.py b/tempest/cli/simple_read_only/image/test_glance.py
index a9cbadb..03e00d7 100644
--- a/tempest/cli/simple_read_only/image/test_glance.py
+++ b/tempest/cli/simple_read_only/image/test_glance.py
@@ -15,9 +15,10 @@
import re
+from tempest_lib import exceptions
+
from tempest import cli
from tempest import config
-from tempest import exceptions
from tempest.openstack.common import log as logging
CONF = config.CONF
@@ -40,6 +41,11 @@
raise cls.skipException(msg)
super(SimpleReadOnlyGlanceClientTest, cls).resource_setup()
+ def glance(self, *args, **kwargs):
+ return self.clients.glance(*args,
+ endpoint_type=CONF.image.endpoint_type,
+ **kwargs)
+
def test_glance_fake_action(self):
self.assertRaises(exceptions.CommandFailed,
self.glance,
diff --git a/tempest/cli/simple_read_only/network/test_neutron.py b/tempest/cli/simple_read_only/network/test_neutron.py
index f9f8906..2b3920d 100644
--- a/tempest/cli/simple_read_only/network/test_neutron.py
+++ b/tempest/cli/simple_read_only/network/test_neutron.py
@@ -15,9 +15,10 @@
import re
+from tempest_lib import exceptions
+
from tempest import cli
from tempest import config
-from tempest import exceptions
from tempest.openstack.common import log as logging
from tempest import test
@@ -41,6 +42,11 @@
raise cls.skipException(msg)
super(SimpleReadOnlyNeutronClientTest, cls).resource_setup()
+ def neutron(self, *args, **kwargs):
+ return self.clients.neutron(*args,
+ endpoint_type=CONF.network.endpoint_type,
+ **kwargs)
+
@test.attr(type='smoke')
def test_neutron_fake_action(self):
self.assertRaises(exceptions.CommandFailed,
diff --git a/tempest/cli/simple_read_only/object_storage/test_swift.py b/tempest/cli/simple_read_only/object_storage/test_swift.py
index a162660..40c4c15 100644
--- a/tempest/cli/simple_read_only/object_storage/test_swift.py
+++ b/tempest/cli/simple_read_only/object_storage/test_swift.py
@@ -15,9 +15,10 @@
import re
+from tempest_lib import exceptions
+
from tempest import cli
from tempest import config
-from tempest import exceptions
CONF = config.CONF
@@ -37,6 +38,10 @@
raise cls.skipException(msg)
super(SimpleReadOnlySwiftClientTest, cls).resource_setup()
+ def swift(self, *args, **kwargs):
+ return self.clients.swift(
+ *args, endpoint_type=CONF.object_storage.endpoint_type, **kwargs)
+
def test_swift_fake_action(self):
self.assertRaises(exceptions.CommandFailed,
self.swift,
diff --git a/tempest/cli/simple_read_only/orchestration/test_heat.py b/tempest/cli/simple_read_only/orchestration/test_heat.py
index 7d7f8c9..d3a9b01 100644
--- a/tempest/cli/simple_read_only/orchestration/test_heat.py
+++ b/tempest/cli/simple_read_only/orchestration/test_heat.py
@@ -42,6 +42,10 @@
os.path.dirname(os.path.realpath(__file__))),
'heat_templates/heat_minimal.yaml')
+ def heat(self, *args, **kwargs):
+ return self.clients.heat(
+ *args, endpoint_type=CONF.orchestration.endpoint_type, **kwargs)
+
def test_heat_stack_list(self):
self.heat('stack-list')
diff --git a/tempest/cli/simple_read_only/telemetry/test_ceilometer.py b/tempest/cli/simple_read_only/telemetry/test_ceilometer.py
index 45b793b..f9bf8b2 100644
--- a/tempest/cli/simple_read_only/telemetry/test_ceilometer.py
+++ b/tempest/cli/simple_read_only/telemetry/test_ceilometer.py
@@ -39,6 +39,10 @@
raise cls.skipException(msg)
super(SimpleReadOnlyCeilometerClientTest, cls).resource_setup()
+ def ceilometer(self, *args, **kwargs):
+ return self.clients.ceilometer(
+ *args, endpoint_type=CONF.telemetry.endpoint_type, **kwargs)
+
def test_ceilometer_meter_list(self):
self.ceilometer('meter-list')
diff --git a/tempest/cli/simple_read_only/volume/test_cinder.py b/tempest/cli/simple_read_only/volume/test_cinder.py
index 45f6c41..6e1e7d3 100644
--- a/tempest/cli/simple_read_only/volume/test_cinder.py
+++ b/tempest/cli/simple_read_only/volume/test_cinder.py
@@ -16,11 +16,12 @@
import logging
import re
+from tempest_lib import exceptions
import testtools
from tempest import cli
from tempest import config
-from tempest import exceptions
+
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -41,6 +42,11 @@
raise cls.skipException(msg)
super(SimpleReadOnlyCinderClientTest, cls).resource_setup()
+ def cinder(self, *args, **kwargs):
+ return self.clients.cinder(*args,
+ endpoint_type=CONF.volume.endpoint_type,
+ **kwargs)
+
def test_cinder_fake_action(self):
self.assertRaises(exceptions.CommandFailed,
self.cinder,
diff --git a/tempest/common/accounts.py b/tempest/common/accounts.py
index 88e8ced..66285e4 100644
--- a/tempest/common/accounts.py
+++ b/tempest/common/accounts.py
@@ -65,6 +65,9 @@
else:
return len(self.hash_dict) > 1
+ def is_multi_tenant(self):
+ return self.is_multi_user()
+
def _create_hash_file(self, hash_string):
path = os.path.join(os.path.join(self.accounts_dir, hash_string))
if not os.path.isfile(path):
@@ -149,13 +152,13 @@
to preserve the current behaviour of the serial tempest run.
"""
- def is_multi_user(self):
+ def _unique_creds(self, cred_arg=None):
+ """Verify that the configured credentials are valid and distinct """
if self.use_default_creds:
- # Verify that the configured users are valid and distinct
try:
user = self.get_primary_creds()
alt_user = self.get_alt_creds()
- return user.username != alt_user.username
+ return getattr(user, cred_arg) != getattr(alt_user, cred_arg)
except exceptions.InvalidCredentials as ic:
msg = "At least one of the configured credentials is " \
"not valid: %s" % ic.message
@@ -164,6 +167,12 @@
# TODO(andreaf) Add a uniqueness check here
return len(self.hash_dict) > 1
+ def is_multi_user(self):
+ return self._unique_creds('username')
+
+ def is_multi_tenant(self):
+ return self._unique_creds('tenant_id')
+
def get_creds(self, id):
try:
# No need to sort the dict as within the same python process
diff --git a/tempest/common/cred_provider.py b/tempest/common/cred_provider.py
index b09c964..c5be0c0 100644
--- a/tempest/common/cred_provider.py
+++ b/tempest/common/cred_provider.py
@@ -48,3 +48,7 @@
@abc.abstractmethod
def is_multi_user(self):
return
+
+ @abc.abstractmethod
+ def is_multi_tenant(self):
+ return
diff --git a/tempest/common/isolated_creds.py b/tempest/common/isolated_creds.py
index 2d16107..228e47c 100644
--- a/tempest/common/isolated_creds.py
+++ b/tempest/common/isolated_creds.py
@@ -354,3 +354,6 @@
def is_multi_user(self):
return True
+
+ def is_multi_tenant(self):
+ return True
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 3a0e975..c290dad 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -37,8 +37,8 @@
MAX_RECURSION_DEPTH = 2
TOKEN_CHARS_RE = re.compile('^[-A-Za-z0-9+/=]*$')
-# All the successful HTTP status codes from RFC 2616
-HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206)
+# All the successful HTTP status codes from RFC 7231 & 4918
+HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206, 207)
# convert a structure into a string safely
@@ -209,8 +209,9 @@
@classmethod
def expected_success(cls, expected_code, read_code):
assert_msg = ("This function only allowed to use for HTTP status"
- "codes which explicitly defined in the RFC 2616. {0}"
- " is not a defined Success Code!").format(expected_code)
+ "codes which explicitly defined in the RFC 7231 & 4918."
+ "{0} is not a defined Success Code!"
+ ).format(expected_code)
if isinstance(expected_code, list):
for code in expected_code:
assert code in HTTP_SUCCESS, assert_msg
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 928a8e1..990a392 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -624,14 +624,20 @@
return floating_ip
def check_floating_ip_status(self, floating_ip, status):
- """Verifies floatingip has reached given status. without waiting
+ """Verifies floatingip reaches the given status
:param floating_ip: net_resources.DeletableFloatingIp floating IP to
to check status
:param status: target status
:raises: AssertionError if status doesn't match
"""
- floating_ip.refresh()
+ def refresh():
+ floating_ip.refresh()
+ return status == floating_ip.status
+
+ tempest.test.call_until_true(refresh,
+ CONF.network.build_timeout,
+ CONF.network.build_interval)
self.assertEqual(status, floating_ip.status,
message="FloatingIP: {fp} is at status: {cst}. "
"failed to reach status: {st}"
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 8a8e387..3725477 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -16,6 +16,7 @@
from tempest.common import custom_matchers
from tempest.common import debug
from tempest import config
+from tempest import exceptions
from tempest.openstack.common import log as logging
from tempest.scenario import manager
from tempest import test
@@ -130,6 +131,17 @@
self.addCleanup(self.servers_client.remove_security_group,
self.server['id'], secgroup['name'])
+ def wait_for_secgroup_add():
+ _, body = self.servers_client.get_server(self.server['id'])
+ return {'name': secgroup['name']} in body['security_groups']
+
+ if not test.call_until_true(wait_for_secgroup_add,
+ CONF.compute.build_timeout,
+ CONF.compute.build_interval):
+ msg = ('Timed out waiting for adding security group %s to server '
+ '%s' % (secgroup['id'], self.server['id']))
+ raise exceptions.TimeoutException(msg)
+
@test.services('compute', 'volume', 'image', 'network')
def test_minimum_basic_scenario(self):
self.glance_image_create()
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 5d75b64..ac4f004 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -179,9 +179,6 @@
"""Verifies connectivty to a VM via public network and floating IP,
and verifies floating IP has resource status is correct.
- Floating IP status is verified after connectivity test in order to
- not add extra waiting and mask racing conditions.
-
:param should_connect: bool. determines if connectivity check is
negative or positive.
:param msg: Failure message to add to Error message. Should describe
diff --git a/tempest/tests/cli/test_cli.py b/tempest/tests/cli/test_cli.py
index 1fd5ccb..8f18dfc 100644
--- a/tempest/tests/cli/test_cli.py
+++ b/tempest/tests/cli/test_cli.py
@@ -13,17 +13,25 @@
# under the License.
import mock
+from tempest_lib.cli import base as cli_base
import testtools
from tempest import cli
+from tempest import config
from tempest import exceptions
from tempest.tests import base
+from tempest.tests import fake_config
class TestMinClientVersion(base.TestCase):
"""Tests for the min_client_version decorator.
"""
+ def setUp(self):
+ super(TestMinClientVersion, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+
def _test_min_version(self, required, installed, expect_skip):
@cli.min_client_version(client='nova', version=required)
@@ -33,7 +41,7 @@
# expected so we need to fail.
self.fail('Should not have gotten past the decorator.')
- with mock.patch.object(cli, 'execute',
+ with mock.patch.object(cli_base, 'execute',
return_value=installed) as mock_cmd:
if expect_skip:
self.assertRaises(testtools.TestCase.skipException, fake,
@@ -41,6 +49,7 @@
else:
fake(self, expect_skip)
mock_cmd.assert_called_once_with('nova', '', params='--version',
+ cli_dir='/usr/local/bin',
merge_stderr=True)
def test_min_client_version(self):
@@ -52,7 +61,7 @@
for case in cases:
self._test_min_version(*case)
- @mock.patch.object(cli, 'execute', return_value=' ')
+ @mock.patch.object(cli_base, 'execute', return_value=' ')
def test_check_client_version_empty_output(self, mock_execute):
# Tests that an exception is raised if the command output is empty.
self.assertRaises(exceptions.TempestException,
diff --git a/tempest/tests/cli/test_command_failed.py b/tempest/tests/cli/test_command_failed.py
deleted file mode 100644
index 36a4fc8..0000000
--- a/tempest/tests/cli/test_command_failed.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# 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 tempest import exceptions
-from tempest.tests import base
-
-
-class TestOutputParser(base.TestCase):
-
- def test_command_failed_exception(self):
- returncode = 1
- cmd = "foo"
- stdout = "output"
- stderr = "error"
- try:
- raise exceptions.CommandFailed(returncode, cmd, stdout, stderr)
- except exceptions.CommandFailed as e:
- self.assertIn(str(returncode), str(e))
- self.assertIn(cmd, str(e))
- self.assertIn(stdout, str(e))
- self.assertIn(stderr, str(e))
diff --git a/tempest/tests/cli/test_output_parser.py b/tempest/tests/cli/test_output_parser.py
deleted file mode 100644
index 7ad270c..0000000
--- a/tempest/tests/cli/test_output_parser.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# Copyright 2014 NEC Corporation.
-# All Rights Reserved.
-#
-# 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 tempest.cli import output_parser
-from tempest import exceptions
-from tempest.tests import base
-
-
-class TestOutputParser(base.TestCase):
- OUTPUT_LINES = """
-+----+------+---------+
-| ID | Name | Status |
-+----+------+---------+
-| 11 | foo | BUILD |
-| 21 | bar | ERROR |
-| 31 | bee | None |
-+----+------+---------+
-"""
- OUTPUT_LINES2 = """
-+----+-------+---------+
-| ID | Name2 | Status2 |
-+----+-------+---------+
-| 41 | aaa | SSSSS |
-| 51 | bbb | TTTTT |
-| 61 | ccc | AAAAA |
-+----+-------+---------+
-"""
-
- EXPECTED_TABLE = {'headers': ['ID', 'Name', 'Status'],
- 'values': [['11', 'foo', 'BUILD'],
- ['21', 'bar', 'ERROR'],
- ['31', 'bee', 'None']]}
- EXPECTED_TABLE2 = {'headers': ['ID', 'Name2', 'Status2'],
- 'values': [['41', 'aaa', 'SSSSS'],
- ['51', 'bbb', 'TTTTT'],
- ['61', 'ccc', 'AAAAA']]}
-
- def test_table_with_normal_values(self):
- actual = output_parser.table(self.OUTPUT_LINES)
- self.assertIsInstance(actual, dict)
- self.assertEqual(self.EXPECTED_TABLE, actual)
-
- def test_table_with_list(self):
- output_lines = self.OUTPUT_LINES.split('\n')
- actual = output_parser.table(output_lines)
- self.assertIsInstance(actual, dict)
- self.assertEqual(self.EXPECTED_TABLE, actual)
-
- def test_table_with_invalid_line(self):
- output_lines = self.OUTPUT_LINES + "aaaa"
- actual = output_parser.table(output_lines)
- self.assertIsInstance(actual, dict)
- self.assertEqual(self.EXPECTED_TABLE, actual)
-
- def test_tables_with_normal_values(self):
- output_lines = 'test' + self.OUTPUT_LINES +\
- 'test2' + self.OUTPUT_LINES2
- expected = [{'headers': self.EXPECTED_TABLE['headers'],
- 'label': 'test',
- 'values': self.EXPECTED_TABLE['values']},
- {'headers': self.EXPECTED_TABLE2['headers'],
- 'label': 'test2',
- 'values': self.EXPECTED_TABLE2['values']}]
- actual = output_parser.tables(output_lines)
- self.assertIsInstance(actual, list)
- self.assertEqual(expected, actual)
-
- def test_tables_with_invalid_values(self):
- output_lines = 'test' + self.OUTPUT_LINES +\
- 'test2' + self.OUTPUT_LINES2 + '\n'
- expected = [{'headers': self.EXPECTED_TABLE['headers'],
- 'label': 'test',
- 'values': self.EXPECTED_TABLE['values']},
- {'headers': self.EXPECTED_TABLE2['headers'],
- 'label': 'test2',
- 'values': self.EXPECTED_TABLE2['values']}]
- actual = output_parser.tables(output_lines)
- self.assertIsInstance(actual, list)
- self.assertEqual(expected, actual)
-
- def test_tables_with_invalid_line(self):
- output_lines = 'test' + self.OUTPUT_LINES +\
- 'test2' + self.OUTPUT_LINES2 +\
- '+----+-------+---------+'
- expected = [{'headers': self.EXPECTED_TABLE['headers'],
- 'label': 'test',
- 'values': self.EXPECTED_TABLE['values']},
- {'headers': self.EXPECTED_TABLE2['headers'],
- 'label': 'test2',
- 'values': self.EXPECTED_TABLE2['values']}]
-
- actual = output_parser.tables(output_lines)
- self.assertIsInstance(actual, list)
- self.assertEqual(expected, actual)
-
- LISTING_OUTPUT = """
-+----+
-| ID |
-+----+
-| 11 |
-| 21 |
-| 31 |
-+----+
-"""
-
- def test_listing(self):
- expected = [{'ID': '11'}, {'ID': '21'}, {'ID': '31'}]
- actual = output_parser.listing(self.LISTING_OUTPUT)
- self.assertIsInstance(actual, list)
- self.assertEqual(expected, actual)
-
- def test_details_multiple_with_invalid_line(self):
- self.assertRaises(exceptions.InvalidStructure,
- output_parser.details_multiple,
- self.OUTPUT_LINES)
-
- DETAILS_LINES1 = """First Table
-+----------+--------+
-| Property | Value |
-+----------+--------+
-| foo | BUILD |
-| bar | ERROR |
-| bee | None |
-+----------+--------+
-"""
- DETAILS_LINES2 = """Second Table
-+----------+--------+
-| Property | Value |
-+----------+--------+
-| aaa | VVVVV |
-| bbb | WWWWW |
-| ccc | XXXXX |
-+----------+--------+
-"""
-
- def test_details_with_normal_line_label_false(self):
- expected = {'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'}
- actual = output_parser.details(self.DETAILS_LINES1)
- self.assertEqual(expected, actual)
-
- def test_details_with_normal_line_label_true(self):
- expected = {'__label': 'First Table',
- 'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'}
- actual = output_parser.details(self.DETAILS_LINES1, with_label=True)
- self.assertEqual(expected, actual)
-
- def test_details_multiple_with_normal_line_label_false(self):
- expected = [{'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'},
- {'aaa': 'VVVVV', 'bbb': 'WWWWW', 'ccc': 'XXXXX'}]
- actual = output_parser.details_multiple(self.DETAILS_LINES1 +
- self.DETAILS_LINES2)
- self.assertIsInstance(actual, list)
- self.assertEqual(expected, actual)
-
- def test_details_multiple_with_normal_line_label_true(self):
- expected = [{'__label': 'First Table',
- 'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'},
- {'__label': 'Second Table',
- 'aaa': 'VVVVV', 'bbb': 'WWWWW', 'ccc': 'XXXXX'}]
- actual = output_parser.details_multiple(self.DETAILS_LINES1 +
- self.DETAILS_LINES2,
- with_label=True)
- self.assertIsInstance(actual, list)
- self.assertEqual(expected, actual)
diff --git a/tempest/tests/stress/test_stress.py b/tempest/tests/stress/test_stress.py
index 5a93472..9c3533d 100644
--- a/tempest/tests/stress/test_stress.py
+++ b/tempest/tests/stress/test_stress.py
@@ -16,7 +16,8 @@
import shlex
import subprocess
-import tempest.exceptions as exceptions
+from tempest_lib import exceptions
+
from tempest.openstack.common import log as logging
from tempest.tests import base
diff --git a/tempest/tests/test_wrappers.py b/tempest/tests/test_wrappers.py
index 0fd41f9..ae7860d 100644
--- a/tempest/tests/test_wrappers.py
+++ b/tempest/tests/test_wrappers.py
@@ -34,7 +34,6 @@
# Setup Test files
self.testr_conf_file = os.path.join(self.directory, '.testr.conf')
self.setup_cfg_file = os.path.join(self.directory, 'setup.cfg')
- self.subunit_trace = os.path.join(self.directory, 'subunit-trace.py')
self.passing_file = os.path.join(self.test_dir, 'test_passing.py')
self.failing_file = os.path.join(self.test_dir, 'test_failing.py')
self.init_file = os.path.join(self.test_dir, '__init__.py')
@@ -45,7 +44,6 @@
shutil.copy('setup.py', self.setup_py)
shutil.copy('tempest/tests/files/setup.cfg', self.setup_cfg_file)
shutil.copy('tempest/tests/files/__init__.py', self.init_file)
- shutil.copy('tools/subunit-trace.py', self.subunit_trace)
# copy over the pretty_tox scripts
shutil.copy('tools/pretty_tox.sh',
os.path.join(self.directory, 'pretty_tox.sh'))
diff --git a/tempest/thirdparty/boto/test_ec2_network.py b/tempest/thirdparty/boto/test_ec2_network.py
index a75fb7b..132a5a8 100644
--- a/tempest/thirdparty/boto/test_ec2_network.py
+++ b/tempest/thirdparty/boto/test_ec2_network.py
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest import test
from tempest.thirdparty.boto import test as boto_test
@@ -22,21 +21,22 @@
@classmethod
def resource_setup(cls):
super(EC2NetworkTest, cls).resource_setup()
- cls.client = cls.os.ec2api_client
+ cls.ec2_client = cls.os.ec2api_client
# Note(afazekas): these tests for things duable without an instance
- @test.skip_because(bug="1080406")
def test_disassociate_not_associated_floating_ip(self):
# EC2 disassociate not associated floating ip
ec2_codes = self.ec2_error_code
- address = self.client.allocate_address()
+ address = self.ec2_client.allocate_address()
public_ip = address.public_ip
- rcuk = self.addResourceCleanUp(self.client.release_address, public_ip)
- addresses_get = self.client.get_all_addresses(addresses=(public_ip,))
+ rcuk = self.addResourceCleanUp(self.ec2_client.release_address,
+ public_ip)
+ addresses_get = self.ec2_client.get_all_addresses(
+ addresses=(public_ip,))
self.assertEqual(len(addresses_get), 1)
self.assertEqual(addresses_get[0].public_ip, public_ip)
self.assertBotoError(ec2_codes.client.InvalidAssociationID.NotFound,
address.disassociate)
- self.client.release_address(public_ip)
- self.cancelResourceCleanUp(rcuk)
+ self.ec2_client.release_address(public_ip)
self.assertAddressReleasedWait(address)
+ self.cancelResourceCleanUp(rcuk)
diff --git a/tools/pretty_tox.sh b/tools/pretty_tox.sh
index 0a04ce6..ff554c5 100755
--- a/tools/pretty_tox.sh
+++ b/tools/pretty_tox.sh
@@ -3,4 +3,4 @@
set -o pipefail
TESTRARGS=$1
-python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | $(dirname $0)/subunit-trace.py --no-failure-debug -f
+python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | subunit-trace --no-failure-debug -f
diff --git a/tools/pretty_tox_serial.sh b/tools/pretty_tox_serial.sh
index db70890..e0fca0f 100755
--- a/tools/pretty_tox_serial.sh
+++ b/tools/pretty_tox_serial.sh
@@ -7,7 +7,7 @@
if [ ! -d .testrepository ]; then
testr init
fi
-testr run --subunit $TESTRARGS | $(dirname $0)/subunit-trace.py -f -n
+testr run --subunit $TESTRARGS | subunit-trace -f -n
retval=$?
testr slowest
diff --git a/tools/subunit-trace.py b/tools/subunit-trace.py
deleted file mode 100755
index 57e58f2..0000000
--- a/tools/subunit-trace.py
+++ /dev/null
@@ -1,247 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2014 Hewlett-Packard Development Company, L.P.
-# Copyright 2014 Samsung Electronics
-# All Rights Reserved.
-#
-# 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.
-
-"""Trace a subunit stream in reasonable detail and high accuracy."""
-
-import argparse
-import functools
-import re
-import sys
-
-import subunit
-import testtools
-
-DAY_SECONDS = 60 * 60 * 24
-FAILS = []
-RESULTS = {}
-
-
-def cleanup_test_name(name, strip_tags=True, strip_scenarios=False):
- """Clean up the test name for display.
-
- By default we strip out the tags in the test because they don't help us
- in identifying the test that is run to it's result.
-
- Make it possible to strip out the testscenarios information (not to
- be confused with tempest scenarios) however that's often needed to
- indentify generated negative tests.
- """
- if strip_tags:
- tags_start = name.find('[')
- tags_end = name.find(']')
- if tags_start > 0 and tags_end > tags_start:
- newname = name[:tags_start]
- newname += name[tags_end + 1:]
- name = newname
-
- if strip_scenarios:
- tags_start = name.find('(')
- tags_end = name.find(')')
- if tags_start > 0 and tags_end > tags_start:
- newname = name[:tags_start]
- newname += name[tags_end + 1:]
- name = newname
-
- return name
-
-
-def get_duration(timestamps):
- start, end = timestamps
- if not start or not end:
- duration = ''
- else:
- delta = end - start
- duration = '%d.%06ds' % (
- delta.days * DAY_SECONDS + delta.seconds, delta.microseconds)
- return duration
-
-
-def find_worker(test):
- for tag in test['tags']:
- if tag.startswith('worker-'):
- return int(tag[7:])
- return 'NaN'
-
-
-# Print out stdout/stderr if it exists, always
-def print_attachments(stream, test, all_channels=False):
- """Print out subunit attachments.
-
- Print out subunit attachments that contain content. This
- runs in 2 modes, one for successes where we print out just stdout
- and stderr, and an override that dumps all the attachments.
- """
- channels = ('stdout', 'stderr')
- for name, detail in test['details'].items():
- # NOTE(sdague): the subunit names are a little crazy, and actually
- # are in the form pythonlogging:'' (with the colon and quotes)
- name = name.split(':')[0]
- if detail.content_type.type == 'test':
- detail.content_type.type = 'text'
- if (all_channels or name in channels) and detail.as_text():
- title = "Captured %s:" % name
- stream.write("\n%s\n%s\n" % (title, ('~' * len(title))))
- # indent attachment lines 4 spaces to make them visually
- # offset
- for line in detail.as_text().split('\n'):
- stream.write(" %s\n" % line)
-
-
-def show_outcome(stream, test, print_failures=False):
- global RESULTS
- status = test['status']
- # TODO(sdague): ask lifeless why on this?
- if status == 'exists':
- return
-
- worker = find_worker(test)
- name = cleanup_test_name(test['id'])
- duration = get_duration(test['timestamps'])
-
- if worker not in RESULTS:
- RESULTS[worker] = []
- RESULTS[worker].append(test)
-
- # don't count the end of the return code as a fail
- if name == 'process-returncode':
- return
-
- if status == 'success':
- stream.write('{%s} %s [%s] ... ok\n' % (
- worker, name, duration))
- print_attachments(stream, test)
- elif status == 'fail':
- FAILS.append(test)
- stream.write('{%s} %s [%s] ... FAILED\n' % (
- worker, name, duration))
- if not print_failures:
- print_attachments(stream, test, all_channels=True)
- elif status == 'skip':
- stream.write('{%s} %s ... SKIPPED: %s\n' % (
- worker, name, test['details']['reason'].as_text()))
- else:
- stream.write('{%s} %s [%s] ... %s\n' % (
- worker, name, duration, test['status']))
- if not print_failures:
- print_attachments(stream, test, all_channels=True)
-
- stream.flush()
-
-
-def print_fails(stream):
- """Print summary failure report.
-
- Currently unused, however there remains debate on inline vs. at end
- reporting, so leave the utility function for later use.
- """
- if not FAILS:
- return
- stream.write("\n==============================\n")
- stream.write("Failed %s tests - output below:" % len(FAILS))
- stream.write("\n==============================\n")
- for f in FAILS:
- stream.write("\n%s\n" % f['id'])
- stream.write("%s\n" % ('-' * len(f['id'])))
- print_attachments(stream, f, all_channels=True)
- stream.write('\n')
-
-
-def count_tests(key, value):
- count = 0
- for k, v in RESULTS.items():
- for item in v:
- if key in item:
- if re.search(value, item[key]):
- count += 1
- return count
-
-
-def run_time():
- runtime = 0.0
- for k, v in RESULTS.items():
- for test in v:
- runtime += float(get_duration(test['timestamps']).strip('s'))
- return runtime
-
-
-def worker_stats(worker):
- tests = RESULTS[worker]
- num_tests = len(tests)
- delta = tests[-1]['timestamps'][1] - tests[0]['timestamps'][0]
- return num_tests, delta
-
-
-def print_summary(stream):
- stream.write("\n======\nTotals\n======\n")
- stream.write("Run: %s in %s sec.\n" % (count_tests('status', '.*'),
- run_time()))
- stream.write(" - Passed: %s\n" % count_tests('status', 'success'))
- stream.write(" - Skipped: %s\n" % count_tests('status', 'skip'))
- stream.write(" - Failed: %s\n" % count_tests('status', 'fail'))
-
- # we could have no results, especially as we filter out the process-codes
- if RESULTS:
- stream.write("\n==============\nWorker Balance\n==============\n")
-
- for w in range(max(RESULTS.keys()) + 1):
- if w not in RESULTS:
- stream.write(
- " - WARNING: missing Worker %s! "
- "Race in testr accounting.\n" % w)
- else:
- num, time = worker_stats(w)
- stream.write(" - Worker %s (%s tests) => %ss\n" %
- (w, num, time))
-
-
-def parse_args():
- parser = argparse.ArgumentParser()
- parser.add_argument('--no-failure-debug', '-n', action='store_true',
- dest='print_failures', help='Disable printing failure '
- 'debug information in realtime')
- parser.add_argument('--fails', '-f', action='store_true',
- dest='post_fails', help='Print failure debug '
- 'information after the stream is proccesed')
- return parser.parse_args()
-
-
-def main():
- args = parse_args()
- stream = subunit.ByteStreamToStreamResult(
- sys.stdin, non_subunit_name='stdout')
- outcomes = testtools.StreamToDict(
- functools.partial(show_outcome, sys.stdout,
- print_failures=args.print_failures))
- summary = testtools.StreamSummary()
- result = testtools.CopyStreamResult([outcomes, summary])
- result.startTestRun()
- try:
- stream.run(result)
- finally:
- result.stopTestRun()
- if count_tests('status', '.*') == 0:
- print("The test run didn't actually run any tests")
- return 1
- if args.post_fails:
- print_fails(sys.stdout)
- print_summary(sys.stdout)
- return (0 if summary.wasSuccessful() else 1)
-
-
-if __name__ == '__main__':
- sys.exit(main())