1953b9df40
This should help investigating on tests timeout issues Change-Id: Idb9953705825de14670cecf529d21f521e24dc76
226 lines
7.0 KiB
Python
226 lines
7.0 KiB
Python
# Copyright (c) 2020 Red Hat
|
|
# 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 __future__ import absolute_import
|
|
|
|
from datetime import datetime
|
|
import os
|
|
import subprocess
|
|
|
|
from oslo_log import log
|
|
from py.xml import html # pylint: disable=no-name-in-module,import-error
|
|
import pytest
|
|
from pytest_html import plugin as html_plugin
|
|
|
|
import tobiko
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
def normalize_path(path):
|
|
return os.path.realpath(os.path.expanduser(path))
|
|
|
|
|
|
TOP_DIR = normalize_path(
|
|
os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
|
|
|
|
|
TOX_REPORT_NAME = os.environ.get('TOX_REPORT_NAME', "tobiko_results")
|
|
|
|
TOX_REPORT_DIR = normalize_path(
|
|
os.environ.get('TOX_REPORT_DIR', os.path.join(TOP_DIR, 'report')))
|
|
|
|
TOX_REPORT_PREFIX = os.path.join(TOX_REPORT_DIR, TOX_REPORT_NAME)
|
|
|
|
TOX_REPORT_HTML = os.environ.get(
|
|
'TOX_REPORT_HTML', TOX_REPORT_PREFIX + '.html')
|
|
|
|
|
|
@pytest.hookimpl
|
|
def pytest_configure(config):
|
|
configure_metadata(config)
|
|
configure_caplog(config)
|
|
configure_timeout(config)
|
|
configure_junitxml(config)
|
|
configure_html(config)
|
|
|
|
|
|
def configure_metadata(config):
|
|
# pylint: disable=protected-access
|
|
from tobiko import version
|
|
config._metadata["Tobiko Version"] = version.release
|
|
git_commit = subprocess.check_output(
|
|
['git', 'log', '-n', '1'],
|
|
universal_newlines=True).replace('\n', '<br>')
|
|
config._metadata["Tobiko Git Commit"] = git_commit
|
|
|
|
|
|
def configure_caplog(config):
|
|
tobiko_config = tobiko.tobiko_config()
|
|
|
|
if tobiko_config.logging.capture_log:
|
|
if tobiko_config.debug:
|
|
level = 'DEBUG'
|
|
else:
|
|
level = 'INFO'
|
|
else:
|
|
level = 'FATAL'
|
|
for key in ['log_level',
|
|
'log_file_level',
|
|
'log_cli_level']:
|
|
set_default_inicfg(config, key, level)
|
|
|
|
line_format: str = tobiko_config.logging.line_format
|
|
if line_format:
|
|
# instance and color are not supported by pytest
|
|
line_format = line_format.replace('%(instance)s', '')
|
|
line_format = line_format.replace('%(color)s', '')
|
|
if line_format:
|
|
for key in ['log_format',
|
|
'log_file_format',
|
|
'log_cli_format']:
|
|
set_default_inicfg(config, key, line_format)
|
|
|
|
date_format = tobiko_config.logging.date_format
|
|
if date_format:
|
|
for key in ['log_date_format',
|
|
'log_file_date_format',
|
|
'log_cli_date_format']:
|
|
set_default_inicfg(config, key, date_format)
|
|
|
|
|
|
def configure_junitxml(config):
|
|
config.inicfg['junit_suite_name'] = TOX_REPORT_NAME
|
|
|
|
|
|
def configure_html(config):
|
|
if config.pluginmanager.hasplugin('html'):
|
|
htmlpath = config.getoption('htmlpath')
|
|
if htmlpath is None:
|
|
config.option.htmlpath = TOX_REPORT_HTML
|
|
htmlpath = config.getoption('htmlpath')
|
|
assert htmlpath is not None
|
|
|
|
html_plugin.HTMLReport = HTMLReport
|
|
|
|
|
|
class HTMLReport(html_plugin.HTMLReport):
|
|
|
|
session = None
|
|
last_html_generation: tobiko.Seconds = None
|
|
|
|
def pytest_sessionstart(self, session):
|
|
super().pytest_sessionstart(session)
|
|
self.session = session
|
|
|
|
def pytest_runtest_logreport(self, report):
|
|
super().pytest_runtest_logreport(report)
|
|
|
|
# Avoid report regeneration ad an interval smaller than 10 seconds
|
|
now = tobiko.time()
|
|
if (self.last_html_generation is not None and
|
|
now - self.last_html_generation < 10.):
|
|
return
|
|
self.last_html_generation = now
|
|
|
|
LOG.debug("Update HTML test report files...")
|
|
temp_report = html_plugin.HTMLReport(logfile=self.logfile,
|
|
config=self.config)
|
|
# pylint: disable=attribute-defined-outside-init
|
|
temp_report.suite_start_time = self.suite_start_time
|
|
temp_report.reports = dict(self.reports)
|
|
temp_report.pytest_sessionfinish(self.session)
|
|
LOG.debug("HTML test report files updated")
|
|
|
|
|
|
def set_default_inicfg(config, key, default):
|
|
value = config.inicfg.setdefault(key, default)
|
|
if value == default:
|
|
LOG.debug(f"Set default inicfg: {key} = {value!r}")
|
|
|
|
|
|
class TestRunnerTimeoutManager(tobiko.SharedFixture):
|
|
timeout: tobiko.Seconds = None
|
|
deadline: tobiko.Seconds = None
|
|
|
|
def setup_fixture(self):
|
|
tobiko_config = tobiko.tobiko_config()
|
|
self.timeout = tobiko_config.testcase.test_runner_timeout
|
|
if self.timeout is None:
|
|
LOG.info('Test runner timeout is disabled')
|
|
else:
|
|
LOG.info('Test runner timeout is enabled: '
|
|
f'timeout is {self.timeout} seconds')
|
|
self.deadline = tobiko.time() + self.timeout
|
|
|
|
@classmethod
|
|
def check_test_runner_timeout(cls):
|
|
self = tobiko.setup_fixture(cls)
|
|
if self.deadline is not None:
|
|
time_left = self.deadline - tobiko.time()
|
|
if time_left <= 0.:
|
|
pytest.skip(
|
|
f"Test runner execution timed out after {self.timeout} "
|
|
f"seconds",
|
|
allow_module_level=True)
|
|
else:
|
|
LOG.debug('Test runner timeout is enabled: '
|
|
f'{time_left} seconds left')
|
|
|
|
|
|
def check_test_runner_timeout():
|
|
TestRunnerTimeoutManager.check_test_runner_timeout()
|
|
|
|
|
|
def configure_timeout(config):
|
|
tobiko_config = tobiko.tobiko_config()
|
|
default = tobiko_config.testcase.timeout
|
|
if default is not None and default > 0.:
|
|
set_default_inicfg(config, 'timeout', default)
|
|
|
|
|
|
def pytest_html_results_table_header(cells):
|
|
cells.insert(2, html.th("Description"))
|
|
cells.insert(1, html.th("Time", class_="sortable time", col="time"))
|
|
cells.pop()
|
|
|
|
|
|
def pytest_html_results_table_row(report, cells):
|
|
cells.insert(2, html.td(getattr(report, 'description', '')))
|
|
cells.insert(1, html.td(datetime.utcnow(), class_="col-time"))
|
|
cells.pop()
|
|
|
|
|
|
@pytest.hookimpl(hookwrapper=True)
|
|
def pytest_runtest_makereport(item, call):
|
|
# pylint: disable=unused-argument
|
|
outcome = yield
|
|
report = outcome.get_result()
|
|
report.description = getattr(item.function, '__doc__', '')
|
|
|
|
|
|
def pytest_html_report_title(report):
|
|
report.title = f"Tobiko test results ({TOX_REPORT_NAME})"
|
|
|
|
|
|
@pytest.hookimpl(hookwrapper=True)
|
|
def pytest_runtest_call(item):
|
|
# pylint: disable=protected-access
|
|
check_test_runner_timeout()
|
|
tobiko.push_test_case(item._testcase)
|
|
try:
|
|
yield
|
|
finally:
|
|
tobiko.pop_test_case()
|