1 tearDownClass()流程
@classmethod
def tearDownClass(cls):
# insert pdb breakpoint when pause_teardown is enabled
if CONF.pause_teardown:
cls.insert_pdb_breakpoint()
at_exit_set.discard(cls)
# It should never be overridden by descendants
if hasattr(super(BaseTestCase, cls), 'tearDownClass'):
super(BaseTestCase, cls).tearDownClass()
# Save any existing exception, we always want to re-raise the original
# exception only
etype, value, trace = sys.exc_info()
# If there was no exception during setup we shall re-raise the first
# exception in teardown
re_raise = (etype is None)
while cls._teardowns:
name, teardown = cls._teardowns.pop()
# Catch any exception in tearDown so we can re-raise the original
# exception at the end
try:
teardown()
if name == 'resources':
if not cls.__resource_cleanup_called:
raise RuntimeError(
"resource_cleanup for %s did not call the "
"super's resource_cleanup" % cls.__name__)
except Exception as te:
sys_exec_info = sys.exc_info()
tetype = sys_exec_info[0]
# TODO(andreaf): Resource cleanup is often implemented by
# storing an array of resources at class level, and cleaning
# them up during `resource_cleanup`.
# In case of failure during setup, some resource arrays might
# not be defined at all, in which case the cleanup code might
# trigger an AttributeError. In such cases we log
# AttributeError as info instead of exception. Once all
# cleanups are migrated to addClassResourceCleanup we can
# remove this.
if tetype is AttributeError and name == 'resources':
LOG.info("tearDownClass of %s failed: %s", name, te)
else:
LOG.exception("teardown of %s failed: %s", name, te)
if not etype:
etype, value, trace = sys_exec_info
# If exceptions were raised during teardown, and not before, re-raise
# the first one
if re_raise and etype is not None:
try:
six.reraise(etype, value, trace)
finally:
del trace # to avoid circular refs
tearDownClass()首先根据配置文件的设置插入pdb断点,在这里加入断点方便资源清理前对结果的分析。
清空at_exit_set,at_exit_set结合validate_tearDownClass函数和atexit,确保即使测试过程中发生异常也能够执行tearDownClass函数来完成资源清理。
cls._teardowns可以理解为栈,其中保存了两个函数cls.resource_cleanup()和cls.clear_credentials()。这两个函数分别用于清理测试资源和凭证资源(如:租户、用户等)。
2 resource_cleanup
@classmethod
def resource_cleanup(cls):
cls.__resource_cleanup_called = True
cleanup_errors = []
while cls._class_cleanups:
try:
fn, args, kwargs = cls._class_cleanups.pop()
fn(*args, **kwargs)
except Exception:
cleanup_errors.append(sys.exc_info())
if cleanup_errors:
raise testtools.MultipleExceptions(*cleanup_errors)
cls._class_cleanups中保存了待执行的资源删除函数,如删除虚机、删除卷等,以函数名,位置参数,关键字参数的三元组形式保存。测试用例的编写过程中创建资源完成后会立即调用cls.addClassResourceCleanup(),将删除资源的信息写入cls._class_cleanups。
整个cls.resource_cleanup()主要是循环执行cls._class_cleanups中的删除操作,执行资源删除前从cls._class_cleanups弹出相关元组。
3 clear_credentials
def clear_creds(self):
if not self._creds:
return
self._clear_isolated_net_resources()
for creds in six.itervalues(self._creds):
try:
self.creds_client.delete_user(creds.user_id)
except lib_exc.NotFound:
LOG.warning("user with name: %s not found for delete",
creds.username)
# NOTE(zhufl): Only when neutron's security_group ext is
# enabled, _cleanup_default_secgroup will not raise error. But
# here cannot use test_utils.is_extension_enabled for it will cause
# "circular dependency". So here just use try...except to
# ensure tenant deletion without big changes.
try:
if self.neutron_available:
self._cleanup_default_secgroup(creds.tenant_id)
except lib_exc.NotFound:
LOG.warning("failed to cleanup tenant %s's secgroup",
creds.tenant_name)
try:
self.creds_client.delete_project(creds.tenant_id)
except lib_exc.NotFound:
LOG.warning("tenant with name: %s not found for delete",
creds.tenant_name)
self._creds = {}
self._creds中保存有凭证信息,self.clear_creds函数删除每个租户的默认安全组和凭证信息。