Implement `pytest.mark.requires_random_entropy` marker for simpler usage.

Signed-off-by: Pedro Algarvio <palgarvio@vmware.com>
This commit is contained in:
Pedro Algarvio 2022-04-08 10:19:54 +01:00 committed by Megan Wilhite
parent daf0db7791
commit af2964f4dd
4 changed files with 103 additions and 33 deletions

View file

@ -261,6 +261,16 @@ def pytest_configure(config):
"markers",
"async_timeout: Timeout, in seconds, for asynchronous test functions(`async def`)",
)
config.addinivalue_line(
"markers",
"requires_random_entropy(minimum={}, timeout={}, skip=True): Mark test as "
"requiring a minimum value of random entropy. In the case where the value is lower "
"than the provided 'minimum', an attempt will be made to raise that value up until "
"the provided 'timeout' minutes have passed, at which time, depending on the value "
"of 'skip' the test will skip or fail.".format(
EntropyGenerator.minimum_entropy, EntropyGenerator.max_minutes
),
)
# "Flag" the slowTest decorator if we're skipping slow tests or not
os.environ["SLOW_TESTS"] = str(config.getoption("--run-slow"))
@ -545,6 +555,55 @@ def pytest_runtest_setup(item):
)
)
requires_random_entropy_marker = item.get_closest_marker("requires_random_entropy")
if requires_random_entropy_marker is not None:
if requires_random_entropy_marker.args:
raise pytest.UsageError(
"'requires_random_entropy' marker does not accept any arguments "
"only keyword arguments."
)
skip = requires_random_entropy_marker.kwargs.pop("skip", None)
if skip and not isinstance(skip, bool):
raise pytest.UsageError(
"The 'skip' keyword argument to the 'requires_random_entropy' marker "
"requires a boolean not '{}'.".format(type(skip))
)
minimum_entropy = requires_random_entropy_marker.kwargs.pop("minimum", None)
if minimum_entropy is not None:
if not isinstance(minimum_entropy, int):
raise pytest.UsageError(
"The 'minimum' keyword argument to the 'requires_random_entropy' marker "
"must be an integer not '{}'.".format(type(minimum_entropy))
)
if minimum_entropy <= 0:
raise pytest.UsageError(
"The 'minimum' keyword argument to the 'requires_random_entropy' marker "
"must be an positive integer not '{}'.".format(minimum_entropy)
)
max_minutes = requires_random_entropy_marker.kwargs.pop("timeout", None)
if max_minutes is not None:
if not isinstance(max_minutes, int):
raise pytest.UsageError(
"The 'timeout' keyword argument to the 'requires_random_entropy' marker "
"must be an integer not '{}'.".format(type(max_minutes))
)
if max_minutes <= 0:
raise pytest.UsageError(
"The 'timeout' keyword argument to the 'requires_random_entropy' marker "
"must be an positive integer not '{}'.".format(max_minutes)
)
if requires_random_entropy_marker.kwargs:
raise pytest.UsageError(
"Unsupported keyword arguments passed to the 'requires_random_entropy' "
"marker: {}".format(
", ".join(list(requires_random_entropy_marker.kwargs))
)
)
entropy_generator = EntropyGenerator(
minimum_entropy=minimum_entropy, max_minutes=max_minutes, skip=skip
)
entropy_generator.generate_entropy()
if salt.utils.platform.is_windows():
unit_tests_paths = (
str(TESTS_DIR / "unit"),

View file

@ -12,6 +12,7 @@ from pytestshellutils.utils.processes import ProcessResult
pytestmark = [
pytest.mark.skip_if_binaries_missing("gpg"),
pytest.mark.requires_random_entropy,
]
log = logging.getLogger(__name__)

View file

@ -15,12 +15,12 @@ import pytest
import salt.modules.gpg as gpg
from salt.exceptions import SaltInvocationError
from tests.support.mock import MagicMock, patch
from tests.support.pytest.helpers import EntropyGenerator
pytest.importorskip("gnupg")
pytestmark = [
pytest.mark.skip_unless_on_linux,
pytest.mark.requires_random_entropy,
]
log = logging.getLogger(__name__)
@ -157,12 +157,6 @@ OZV2Hg+93dg3Wi6g/JW4OuTKWKuHRqpRB1J4i4lO
"""
@pytest.fixture(autouse=True)
def entropy_generation():
with EntropyGenerator():
yield
@pytest.fixture
def gpghome(tmp_path):
root = tmp_path / "gpghome"

View file

@ -614,9 +614,18 @@ class FakeSaltExtension:
class EntropyGenerator:
def __init__(self, max_minutes=5, minimum_entropy=800):
self.max_minutes = max_minutes
self.minimum_entropy = minimum_entropy
max_minutes = 5
minimum_entropy = 800
def __init__(self, max_minutes=None, minimum_entropy=None, skip=None):
if max_minutes is not None:
self.max_minutes = max_minutes
if minimum_entropy is not None:
self.minimum_entropy = minimum_entropy
if skip is None:
skip = True
self.skip = skip
self.current_entropy = 0
def generate_entropy(self):
max_time = self.max_minutes * 60
@ -625,9 +634,9 @@ class EntropyGenerator:
log.info("The '%s' file is not avilable", kernel_entropy_file)
return
available_entropy = int(kernel_entropy_file.read_text().strip())
log.info("Available Entropy: %s", available_entropy)
if available_entropy >= self.minimum_entropy:
self.current_entropy = int(kernel_entropy_file.read_text().strip())
log.info("Available Entropy: %s", self.current_entropy)
if self.current_entropy >= self.minimum_entropy:
return
exc_kwargs = {}
@ -641,29 +650,33 @@ class EntropyGenerator:
log.info("Using rngd to generate entropy")
while True:
if time.time() >= timeout:
raise pytest.skip.Exception(
message = (
"Skipping test as generating entropy took more than {} minutes. "
"Current entropy value {}".format(
self.max_minutes, available_entropy
),
**exc_kwargs
self.max_minutes, self.current_entropy
)
)
if self.skip:
raise pytest.skip.Exception(message, **exc_kwargs)
raise pytest.fail(message)
subprocess.run([rngd, "-r", "/dev/urandom"], shell=False, check=True)
available_entropy = int(kernel_entropy_file.read_text().strip())
log.info("Available Entropy: %s", available_entropy)
if available_entropy >= self.minimum_entropy:
self.current_entropy = int(kernel_entropy_file.read_text().strip())
log.info("Available Entropy: %s", self.current_entropy)
if self.current_entropy >= self.minimum_entropy:
break
elif openssl:
log.info("Using openssl to generate entropy")
while True:
if time.time() >= timeout:
raise pytest.skip.Exception(
message = (
"Skipping test as generating entropy took more than {} minutes. "
"Current entropy value {}".format(
self.max_minutes, available_entropy
),
**exc_kwargs
self.max_minutes, self.current_entropy
)
)
if self.skip:
raise pytest.skip.Exception(message, **exc_kwargs)
raise pytest.fail(message)
target_file = tempfile.NamedTemporaryFile(
delete=False, suffix="sample.txt"
@ -671,7 +684,7 @@ class EntropyGenerator:
target_file.close()
subprocess.run(
[
"openssl",
openssl,
"rand",
"-out",
target_file.name,
@ -682,17 +695,20 @@ class EntropyGenerator:
check=True,
)
os.unlink(target_file.name)
available_entropy = int(kernel_entropy_file.read_text().strip())
log.info("Available Entropy: %s", available_entropy)
if available_entropy >= self.minimum_entropy:
self.current_entropy = int(kernel_entropy_file.read_text().strip())
log.info("Available Entropy: %s", self.current_entropy)
if self.current_entropy >= self.minimum_entropy:
break
else:
raise pytest.skip.Exception(
"Skipping test as there's not enough entropy({}) to continue".format(
available_entropy
),
**exc_kwargs
message = (
"Skipping test as there's not enough entropy({}) to continue and "
"neither 'rgn-tools' nor 'openssl' is available on the system.".format(
self.current_entropy
)
)
if self.skip:
raise pytest.skip.Exception(message, **exc_kwargs)
raise pytest.fail(message)
def __enter__(self):
self.generate_entropy()