Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
Change-Id: I2276c41cda60bda64a46e72ecef285149471225d
This commit is contained in:
Stephen Finucane
2025-11-04 17:25:02 +00:00
parent b6f74b6754
commit b5e4a4c3b4
18 changed files with 174 additions and 111 deletions

View File

@@ -12,13 +12,14 @@ repos:
- id: debug-statements
- id: check-yaml
files: .*\.(yaml|yml)$
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.12
hooks:
- id: ruff-check
args: ['--fix', '--unsafe-fixes']
- id: ruff-format
- repo: https://opendev.org/openstack/hacking
rev: 7.0.0
hooks:
- id: hacking
additional_dependencies: []
- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
hooks:
- id: pyupgrade
args: [--py310-plus]

View File

@@ -57,7 +57,5 @@ openstackdocs_bug_tag = ''
# sphinxcontrib.apidoc options
apidoc_module_dir = '../../oslotest'
apidoc_output_dir = 'reference/api'
apidoc_excluded_paths = [
'tests/*',
'tests']
apidoc_excluded_paths = ['tests/*', 'tests']
apidoc_separate_modules = True

View File

@@ -84,6 +84,7 @@ class BaseTestCase(testtools.TestCase):
.. _fixtures: https://pypi.org/project/fixtures
"""
DEFAULT_TIMEOUT = 0
TIMEOUT_SCALING_FACTOR = 1
@@ -100,8 +101,7 @@ class BaseTestCase(testtools.TestCase):
# NOTE(dims): This is a hack for Mitaka. We'll need to undo this as
# early as possible in Newton and advertise that this hack will not
# be supported anymore.
if (hasattr(self, '_cleanups') and
isinstance(self._cleanups, list)):
if hasattr(self, '_cleanups') and isinstance(self._cleanups, list):
if not self._cleanups:
# Ensure that the mock.patch.stopall cleanup is registered
# before any addCleanup() methods have a chance to register
@@ -110,8 +110,10 @@ class BaseTestCase(testtools.TestCase):
# method so those mocks are not included in the stopall set.
super().addCleanup(mock.patch.stopall)
else:
LOG.error('Unable to patch test case. '
'mock.patch.stopall cleanup was not registered.')
LOG.error(
'Unable to patch test case. '
'mock.patch.stopall cleanup was not registered.'
)
super().addCleanup(function, *args, **kwargs)
def setUp(self):
@@ -123,10 +125,12 @@ class BaseTestCase(testtools.TestCase):
self.useFixture(fixtures.TempHomeDir())
def _set_timeout(self):
self.useFixture(timeout.Timeout(
default_timeout=self.DEFAULT_TIMEOUT,
scaling_factor=self.TIMEOUT_SCALING_FACTOR,
))
self.useFixture(
timeout.Timeout(
default_timeout=self.DEFAULT_TIMEOUT,
scaling_factor=self.TIMEOUT_SCALING_FACTOR,
)
)
def _fake_output(self):
self.output_fixture = self.useFixture(output.CaptureOutput())
@@ -155,11 +159,13 @@ class BaseTestCase(testtools.TestCase):
else:
basename, contents = f
encoding = default_encoding
fix = self.useFixture(createfile.CreateFileWithContent(
filename=basename,
contents=contents,
ext=ext,
encoding=encoding,
))
fix = self.useFixture(
createfile.CreateFileWithContent(
filename=basename,
contents=contents,
ext=ext,
encoding=encoding,
)
)
tempfiles.append(fix.path)
return tempfiles

View File

@@ -58,8 +58,9 @@ class CreateFileWithContent(fixtures.Fixture):
if isinstance(contents, str):
contents = contents.encode(self._encoding)
if not os.path.isabs(self._filename):
(fd, self.path) = tempfile.mkstemp(prefix=self._filename,
suffix=self._ext)
fd, self.path = tempfile.mkstemp(
prefix=self._filename, suffix=self._ext
)
else:
self.path = self._filename + self._ext
fd = os.open(self.path, os.O_CREAT | os.O_WRONLY)

View File

@@ -19,9 +19,11 @@ _TRUE_VALUES = ('True', 'true', '1', 'yes')
_FALSE_VALUES = ('False', 'false', '0', 'no')
_BASE_LOG_LEVELS = ('DEBUG', 'INFO', 'WARN', 'WARNING', 'ERROR', 'CRITICAL')
_LOG_LEVELS = {n: getattr(logging, n) for n in _BASE_LOG_LEVELS}
_LOG_LEVELS.update({
'TRACE': 5,
})
_LOG_LEVELS.update(
{
'TRACE': 5,
}
)
def _try_int(value):
@@ -73,7 +75,7 @@ class ConfigureLogging(fixtures.Fixture):
elif _os_debug in _LOG_LEVELS:
self.level = _LOG_LEVELS[_os_debug]
elif _os_debug and _os_debug not in _FALSE_VALUES:
raise ValueError('OS_DEBUG=%s is invalid.' % (_os_debug))
raise ValueError(f'OS_DEBUG={_os_debug} is invalid.')
self.capture_logs = os.environ.get('OS_LOG_CAPTURE') in _TRUE_VALUES
self.logger = None

View File

@@ -72,8 +72,7 @@ class _AutospecMockMixin:
if callable(original_attr):
# lazily autospec callable attribute.
eat_self = mock._must_skip(
original_spec, name,
isinstance(original_spec, type)
original_spec, name, isinstance(original_spec, type)
)
_lazy_autospec_method(attr, original_attr, eat_self)
@@ -112,13 +111,11 @@ class MockAutospecFixture(fixtures.Fixture):
# patch both external and internal usage of Mock / MagicMock.
self.useFixture(
fixtures.MonkeyPatch(
'unittest.mock.Mock',
_AutospecMock))
fixtures.MonkeyPatch('unittest.mock.Mock', _AutospecMock)
)
self.useFixture(
fixtures.MonkeyPatch(
'unittest.mock.MagicMock',
_AutospecMagicMock))
fixtures.MonkeyPatch('unittest.mock.MagicMock', _AutospecMagicMock)
)
class _patch(mock._patch):
@@ -140,8 +137,9 @@ class _patch(mock._patch):
autospec = True if self.autospec is None else self.autospec
# in some cases, autospec cannot be set to True.
skip_autospec = (getattr(self, attr) for attr in
['new_callable', 'create', 'spec'])
skip_autospec = (
getattr(self, attr) for attr in ['new_callable', 'create', 'spec']
)
# NOTE(claudiub): The "new" argument is always mock.DEFAULT, unless
# explicitly set otherwise.
if self.new is not mock.DEFAULT or any(skip_autospec):
@@ -163,17 +161,16 @@ class _patch(mock._patch):
target = self.getter()
original_attr = getattr(target, self.attribute)
eat_self = mock._must_skip(
target,
self.attribute,
isinstance(target, type)
target, self.attribute, isinstance(target, type)
)
new = super().__enter__()
# NOTE(claudiub): mock.patch.multiple will cause new to be a
# dict.
mocked_method = (new[self.attribute] if isinstance(new, dict)
else new)
mocked_method = (
new[self.attribute] if isinstance(new, dict) else new
)
_lazy_autospec_method(mocked_method, original_attr, eat_self)
return new
else:
@@ -199,4 +196,5 @@ def patch_mock_module():
# as it will try to copy the partial function's __name__ (which they do
# not have).
mock._copy_func_details = _safe_attribute_error_wrapper(
mock._copy_func_details)
mock._copy_func_details
)

View File

@@ -47,13 +47,15 @@ class CaptureOutput(fixtures.Fixture):
def __init__(self, do_stdout=None, do_stderr=None):
super().__init__()
if do_stdout is None:
self.do_stdout = (os.environ.get('OS_STDOUT_CAPTURE')
in _TRUE_VALUES)
self.do_stdout = (
os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES
)
else:
self.do_stdout = do_stdout
if do_stderr is None:
self.do_stderr = (os.environ.get('OS_STDERR_CAPTURE')
in _TRUE_VALUES)
self.do_stderr = (
os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES
)
else:
self.do_stderr = do_stderr
self.stdout = None

View File

@@ -24,7 +24,6 @@ from oslotest import base
class TestBaseTestCase(testtools.TestCase):
class FakeTestCase(base.BaseTestCase):
def test_fake_test(self):
pass
@@ -44,7 +43,9 @@ class TestBaseTestCase(testtools.TestCase):
def test_fake_logs_default(self, env_get_mock):
# without debug and log capture
env_get_mock.side_effect = lambda value, default=None: {
'OS_DEBUG': 0, 'OS_LOG_CAPTURE': 0}.get(value, default)
'OS_DEBUG': 0,
'OS_LOG_CAPTURE': 0,
}.get(value, default)
tc = self.FakeTestCase("test_fake_test")
tc.setUp()
env_get_mock.assert_any_call('OS_LOG_CAPTURE')
@@ -56,20 +57,24 @@ class TestBaseTestCase(testtools.TestCase):
@mock.patch('logging.basicConfig')
def test_fake_logs_with_debug(self, basic_logger_mock, env_get_mock):
env_get_mock.side_effect = lambda value, default=None: {
'OS_DEBUG': 'True', 'OS_LOG_CAPTURE': 0}.get(value, default)
'OS_DEBUG': 'True',
'OS_LOG_CAPTURE': 0,
}.get(value, default)
tc = self.FakeTestCase("test_fake_test")
tc.setUp()
env_get_mock.assert_any_call('OS_LOG_CAPTURE')
env_get_mock.assert_any_call('OS_DEBUG')
basic_logger_mock.assert_called_once_with(format=base._LOG_FORMAT,
level=logging.DEBUG)
basic_logger_mock.assert_called_once_with(
format=base._LOG_FORMAT, level=logging.DEBUG
)
@mock.patch('os.environ.get')
@mock.patch.object(FakeTestCase, 'useFixture')
def test_fake_logs_with_log_cap(self, fixture_mock, env_get_mock):
env_get_mock.side_effect = lambda value: {'OS_DEBUG': 0,
'OS_LOG_CAPTURE': 'True'
}.get(value)
env_get_mock.side_effect = lambda value: {
'OS_DEBUG': 0,
'OS_LOG_CAPTURE': 'True',
}.get(value)
tc = self.FakeTestCase("test_fake_test")
tc.setUp()
env_get_mock.assert_any_call('OS_LOG_CAPTURE')
@@ -114,7 +119,6 @@ class TestBaseTestCase(testtools.TestCase):
class TestManualMock(base.BaseTestCase):
def setUp(self):
# Create a cleanup to undo a patch() call *before* calling the
# base class version of setup().
@@ -161,8 +165,9 @@ class TestTempFiles(base.BaseTestCase):
contents = f.read()
if not isinstance(raw_contents, str):
raw_contents = str(raw_contents, encoding=raw_encoding)
self.assertEqual(str(contents, encoding=raw_encoding),
raw_contents)
self.assertEqual(
str(contents, encoding=raw_encoding), raw_contents
)
def test_create_bad_encoding(self):
files = [["hrm", 'ಠ~ಠ', 'ascii']]

View File

@@ -19,7 +19,6 @@ from oslotest import createfile
class CreateFileWithContentTest(base.BaseTestCase):
def test_create_unicode_files(self):
f = createfile.CreateFileWithContent(
"no_approve",
@@ -32,7 +31,9 @@ class CreateFileWithContentTest(base.BaseTestCase):
def test_create_unicode_files_encoding(self):
f = createfile.CreateFileWithContent(
"embarrassed", '⊙﹏⊙', encoding='utf-8',
"embarrassed",
'⊙﹏⊙',
encoding='utf-8',
)
f.setUp()
with open(f.path, 'rb') as f:
@@ -41,7 +42,9 @@ class CreateFileWithContentTest(base.BaseTestCase):
def test_create_bad_encoding(self):
f = createfile.CreateFileWithContent(
"hrm", 'ಠ~ಠ', encoding='ascii',
"hrm",
'ಠ~ಠ',
encoding='ascii',
)
self.assertRaises(UnicodeError, f.setUp)

View File

@@ -21,12 +21,13 @@ from oslotest import log
class ConfigureLoggingTestCase(testtools.TestCase):
@mock.patch('os.environ.get')
def test_fake_logs_default(self, env_get_mock):
# without debug and log capture
env_get_mock.side_effect = lambda value, default=None: {
'OS_DEBUG': 0, 'OS_LOG_CAPTURE': 0}.get(value, default)
'OS_DEBUG': 0,
'OS_LOG_CAPTURE': 0,
}.get(value, default)
f = log.ConfigureLogging()
f.setUp()
env_get_mock.assert_any_call('OS_LOG_CAPTURE')
@@ -38,59 +39,68 @@ class ConfigureLoggingTestCase(testtools.TestCase):
@mock.patch('logging.basicConfig')
def test_fake_logs_with_debug(self, basic_logger_mock, env_get_mock):
env_get_mock.side_effect = lambda value, default=None: {
'OS_DEBUG': 'True', 'OS_LOG_CAPTURE': 0}.get(value, default)
'OS_DEBUG': 'True',
'OS_LOG_CAPTURE': 0,
}.get(value, default)
f = log.ConfigureLogging()
f.setUp()
env_get_mock.assert_any_call('OS_LOG_CAPTURE')
env_get_mock.assert_any_call('OS_DEBUG')
basic_logger_mock.assert_called_once_with(
format=log.ConfigureLogging.DEFAULT_FORMAT,
level=logging.DEBUG)
format=log.ConfigureLogging.DEFAULT_FORMAT, level=logging.DEBUG
)
@mock.patch('os.environ.get')
@mock.patch('logging.basicConfig')
def test_fake_logs_with_warning(self, basic_logger_mock, env_get_mock):
env_get_mock.side_effect = lambda value, default=None: {
'OS_DEBUG': 'WARNING', 'OS_LOG_CAPTURE': 0}.get(value, default)
'OS_DEBUG': 'WARNING',
'OS_LOG_CAPTURE': 0,
}.get(value, default)
f = log.ConfigureLogging()
f.setUp()
env_get_mock.assert_any_call('OS_LOG_CAPTURE')
env_get_mock.assert_any_call('OS_DEBUG')
basic_logger_mock.assert_called_once_with(
format=log.ConfigureLogging.DEFAULT_FORMAT,
level=logging.WARNING)
format=log.ConfigureLogging.DEFAULT_FORMAT, level=logging.WARNING
)
@mock.patch('os.environ.get')
@mock.patch('logging.basicConfig')
def test_fake_logs_with_trace_int(self, basic_logger_mock, env_get_mock):
env_get_mock.side_effect = lambda value, default=None: {
'OS_DEBUG': '5', 'OS_LOG_CAPTURE': 0}.get(value, default)
'OS_DEBUG': '5',
'OS_LOG_CAPTURE': 0,
}.get(value, default)
f = log.ConfigureLogging()
f.setUp()
env_get_mock.assert_any_call('OS_LOG_CAPTURE')
env_get_mock.assert_any_call('OS_DEBUG')
basic_logger_mock.assert_called_once_with(
format=log.ConfigureLogging.DEFAULT_FORMAT,
level=5)
format=log.ConfigureLogging.DEFAULT_FORMAT, level=5
)
@mock.patch('os.environ.get')
@mock.patch('logging.basicConfig')
def test_fake_logs_with_debug_int(self, basic_logger_mock, env_get_mock):
env_get_mock.side_effect = lambda value, default=None: {
'OS_DEBUG': '10', 'OS_LOG_CAPTURE': 0}.get(value, default)
'OS_DEBUG': '10',
'OS_LOG_CAPTURE': 0,
}.get(value, default)
f = log.ConfigureLogging()
f.setUp()
env_get_mock.assert_any_call('OS_LOG_CAPTURE')
env_get_mock.assert_any_call('OS_DEBUG')
basic_logger_mock.assert_called_once_with(
format=log.ConfigureLogging.DEFAULT_FORMAT,
level=logging.DEBUG)
format=log.ConfigureLogging.DEFAULT_FORMAT, level=logging.DEBUG
)
@mock.patch('os.environ.get')
def test_fake_logs_with_log_capture(self, env_get_mock):
env_get_mock.side_effect = lambda value: {'OS_DEBUG': 0,
'OS_LOG_CAPTURE': 'True'
}[value]
env_get_mock.side_effect = lambda value: {
'OS_DEBUG': 0,
'OS_LOG_CAPTURE': 'True',
}[value]
f = log.ConfigureLogging()
f.setUp()
env_get_mock.assert_any_call('OS_LOG_CAPTURE')

View File

@@ -40,7 +40,6 @@ class Foo:
class MockSanityTestCase(testtools.TestCase):
def setUp(self):
super().setUp()
self.useFixture(mock_fixture.MockAutospecFixture())
@@ -51,21 +50,39 @@ class MockSanityTestCase(testtools.TestCase):
# check that the methods are callable with correct args.
mock_method(mock.sentinel.a, mock.sentinel.b, mock.sentinel.c)
mock_method(mock.sentinel.a, mock.sentinel.b, mock.sentinel.c,
d=mock.sentinel.d)
mock_method.assert_has_calls([
mock.call(mock.sentinel.a, mock.sentinel.b, mock.sentinel.c),
mock.call(mock.sentinel.a, mock.sentinel.b, mock.sentinel.c,
d=mock.sentinel.d)])
mock_method(
mock.sentinel.a,
mock.sentinel.b,
mock.sentinel.c,
d=mock.sentinel.d,
)
mock_method.assert_has_calls(
[
mock.call(
mock.sentinel.a, mock.sentinel.b, mock.sentinel.c
),
mock.call(
mock.sentinel.a,
mock.sentinel.b,
mock.sentinel.c,
d=mock.sentinel.d,
),
]
)
# assert that TypeError is raised if the method signature is not
# respected.
self.assertRaises(TypeError, mock_method)
self.assertRaises(TypeError, mock_method, mock.sentinel.a)
self.assertRaises(TypeError, mock_method, a=mock.sentinel.a)
self.assertRaises(TypeError, mock_method, mock.sentinel.a,
mock.sentinel.b, mock.sentinel.c,
e=mock.sentinel.e)
self.assertRaises(
TypeError,
mock_method,
mock.sentinel.a,
mock.sentinel.b,
mock.sentinel.c,
e=mock.sentinel.e,
)
# assert that AttributeError is raised if the method does not exist.
self.assertRaises(AttributeError, getattr, foo, 'lish')
@@ -90,17 +107,21 @@ class MockSanityTestCase(testtools.TestCase):
self._check_autospeced_foo(foo)
def test_patch_autospec_multiple(self):
with mock.patch.multiple(Foo, bar=mock.DEFAULT,
classic_bar=mock.DEFAULT,
static_bar=mock.DEFAULT):
with mock.patch.multiple(
Foo,
bar=mock.DEFAULT,
classic_bar=mock.DEFAULT,
static_bar=mock.DEFAULT,
):
foo = Foo()
self._check_autospeced_foo(foo)
@mock.patch.object(Foo, 'static_bar', autospec=False)
@mock.patch.object(Foo, 'classic_bar', autospec=False)
@mock.patch.object(Foo, 'bar', autospec=False)
def test_patch_autospec_class_false(self, mock_meth, mock_cmeth,
mock_smeth):
def test_patch_autospec_class_false(
self, mock_meth, mock_cmeth, mock_smeth
):
foo = Foo()
# we're checking that method signature is not enforced.
foo.bar()

View File

@@ -15,9 +15,7 @@ from oslotest import modules
class CreateDisableModuleTest(base.BaseTestCase):
def test_disable_module(self):
s = __import__('sys')
self.assertTrue(s)

View File

@@ -19,7 +19,6 @@ from oslotest import output
class CaptureOutputTest(testtools.TestCase):
@mock.patch('os.environ')
def test_disabled_env(self, mock_env):
mock_env.get.return_value = ''

View File

@@ -18,7 +18,6 @@ from oslotest import timeout
class TimeoutTestCase(testtools.TestCase):
@mock.patch('os.environ.get')
@mock.patch.object(timeout.Timeout, 'useFixture')
@mock.patch('fixtures.Timeout')
@@ -33,8 +32,9 @@ class TimeoutTestCase(testtools.TestCase):
@mock.patch('os.environ.get')
@mock.patch.object(timeout.Timeout, 'useFixture')
@mock.patch('fixtures.Timeout')
def test_no_timeout(self, fixture_timeout_mock, fixture_mock,
env_get_mock):
def test_no_timeout(
self, fixture_timeout_mock, fixture_mock, env_get_mock
):
# Returning 0 means we don't install the timeout
env_get_mock.return_value = 0
tc = timeout.Timeout()
@@ -47,7 +47,8 @@ class TimeoutTestCase(testtools.TestCase):
@mock.patch.object(timeout.Timeout, 'useFixture')
@mock.patch('fixtures.Timeout')
def test_timeout_default(
self, fixture_timeout_mock, fixture_mock, env_get_mock):
self, fixture_timeout_mock, fixture_mock, env_get_mock
):
env_get_mock.return_value = 5
tc = timeout.Timeout(default_timeout=5)
tc.setUp()
@@ -59,7 +60,8 @@ class TimeoutTestCase(testtools.TestCase):
@mock.patch.object(timeout.Timeout, 'useFixture')
@mock.patch('fixtures.Timeout')
def test_timeout_bad_default(
self, fixture_timeout_mock, fixture_mock, env_get_mock):
self, fixture_timeout_mock, fixture_mock, env_get_mock
):
env_get_mock.return_value = 'invalid'
tc = timeout.Timeout(default_timeout='invalid')
tc.setUp()
@@ -71,7 +73,8 @@ class TimeoutTestCase(testtools.TestCase):
@mock.patch.object(timeout.Timeout, 'useFixture')
@mock.patch('fixtures.Timeout')
def test_timeout_scaling(
self, fixture_timeout_mock, fixture_mock, env_get_mock):
self, fixture_timeout_mock, fixture_mock, env_get_mock
):
env_get_mock.return_value = 2
tc = timeout.Timeout(scaling_factor=1.5)
tc.setUp()
@@ -83,7 +86,8 @@ class TimeoutTestCase(testtools.TestCase):
@mock.patch.object(timeout.Timeout, 'useFixture')
@mock.patch('fixtures.Timeout')
def test_timeout_bad_scaling(
self, fixture_timeout_mock, fixture_mock, env_get_mock):
self, fixture_timeout_mock, fixture_mock, env_get_mock
):
env_get_mock.return_value = 2
tc = timeout.Timeout(scaling_factor='invalid')
tc.setUp()

View File

@@ -10,8 +10,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Utilities functions for working with oslo.config from the tool scripts.
"""
"""Utilities functions for working with oslo.config from the tool scripts."""
import os
@@ -38,9 +37,7 @@ def get_config_parser():
def parse_arguments(conf):
# Look for a few configuration files, and load the ones we find.
default_config_files = [
f
for f in DEFAULT_CONFIG_FILES
if os.path.exists(f)
f for f in DEFAULT_CONFIG_FILES if os.path.exists(f)
]
return conf(
project='oslo',

View File

@@ -38,3 +38,17 @@ packages = [
script-files = [
"tools/oslo_debug_helper",
]
[tool.ruff]
line-length = 79
[tool.ruff.format]
quote-style = "preserve"
docstring-code-format = true
[tool.ruff.lint]
select = ["E4", "E5", "E7", "E9", "F", "S", "U"]
ignore = [
# we only use asserts for type narrowing
"S101",
]

View File

@@ -17,4 +17,5 @@ import setuptools
setuptools.setup(
setup_requires=['pbr>=2.0.0'],
pbr=True)
pbr=True,
)

View File

@@ -34,7 +34,6 @@ deps =
commands =
sphinx-build -W --keep-going -b html -d doc/build/doctrees doc/source doc/build/html
[testenv:releasenotes]
deps =
{[testenv:docs]deps}
@@ -42,5 +41,9 @@ commands =
sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
[flake8]
# We only enable the hacking (H) checks
select = H,O
# H301 Black will put commas after imports that can't fit on one line
ignore = H301
show-source = true
exclude = .tox,dist,doc,*.egg,build