From 97964886422e6bc6184666ce603a688514395d09 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Sat, 14 Mar 2026 16:48:34 +0100 Subject: [PATCH 1/4] pyocf: Use global random seed in all the tests - Make tests more reproducible. - Enable using pytest-xdist (5x speed up on 16-core AMD 7955WX). Co-developed-by: Claude Opus 4.6 Signed-off-by: Claude Opus 4.6 Signed-off-by: Robert Baldyga --- tests/functional/tests/engine/test_read.py | 8 ++++---- .../tests/management/test_composite_volume.py | 18 ++++++++---------- tests/functional/tests/utils/random.py | 12 ++++++++---- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/functional/tests/engine/test_read.py b/tests/functional/tests/engine/test_read.py index 693bd71d8..b28e38a0a 100644 --- a/tests/functional/tests/engine/test_read.py +++ b/tests/functional/tests/engine/test_read.py @@ -1,6 +1,7 @@ # # Copyright(c) 2019-2022 Intel Corporation # Copyright(c) 2024 Huawei Technologies +# Copyright(c) 2026 Unvertical # SPDX-License-Identifier: BSD-3-Clause # @@ -11,7 +12,7 @@ import pytest import random from hashlib import md5 -from datetime import datetime +from tests.utils.random import get_random_seed from pyocf.types.cache import Cache, CacheMode from pyocf.types.core import Core @@ -185,8 +186,7 @@ def print_test_case( @pytest.mark.parametrize("cacheline_size", CacheLineSize) @pytest.mark.parametrize("cache_mode", CacheMode) -@pytest.mark.parametrize("rand_seed", [datetime.now().timestamp()]) -def test_read_data_consistency(pyocf_ctx, cacheline_size, cache_mode, rand_seed): +def test_read_data_consistency(pyocf_ctx, cacheline_size, cache_mode): CACHELINE_COUNT = 9 SECTOR_SIZE = Size.from_sector(1).B CLS = cacheline_size // SECTOR_SIZE @@ -195,7 +195,7 @@ def test_read_data_consistency(pyocf_ctx, cacheline_size, cache_mode, rand_seed) SECTOR_COUNT = int(WORKSET_SIZE / SECTOR_SIZE) ITRATION_COUNT = 50 - random.seed(rand_seed) + random.seed(get_random_seed()) # start sector for each region (positions of '*' on the above diagram) region_start = ( diff --git a/tests/functional/tests/management/test_composite_volume.py b/tests/functional/tests/management/test_composite_volume.py index 31fd924fb..37158ab55 100644 --- a/tests/functional/tests/management/test_composite_volume.py +++ b/tests/functional/tests/management/test_composite_volume.py @@ -9,7 +9,8 @@ import random import time from ctypes import POINTER, c_int, cast, c_void_p -from datetime import datetime, timedelta +from datetime import timedelta +from tests.utils.random import get_random_seed from threading import Event from collections import namedtuple @@ -623,8 +624,7 @@ def test_io_propagation_entire_dev(pyocf_ctx): cvol.destroy() -@pytest.mark.parametrize("rand_seed", [datetime.now().timestamp()]) -def test_io_propagation_multiple_subvolumes(pyocf_ctx, rand_seed): +def test_io_propagation_multiple_subvolumes(pyocf_ctx): """ title: Perform multi-subvolume operations. description: | @@ -646,7 +646,7 @@ def test_io_propagation_multiple_subvolumes(pyocf_ctx, rand_seed): requirements: - composite_volume::io_request_passing """ - random.seed(rand_seed) + random.seed(get_random_seed()) pyocf_ctx.register_volume_type(TraceDevice) vol_size = S.from_MiB(1) @@ -731,8 +731,7 @@ def test_io_propagation_multiple_subvolumes(pyocf_ctx, rand_seed): cvol.destroy() -@pytest.mark.parametrize("rand_seed", [datetime.now().timestamp()]) -def test_io_completion(pyocf_ctx, rand_seed): +def test_io_completion(pyocf_ctx): """ title: Composite volume completion order. description: | @@ -754,7 +753,7 @@ def test_io_completion(pyocf_ctx, rand_seed): requirements: - composite_volume::io_request_completion """ - random.seed(rand_seed) + random.seed(get_random_seed()) class PendingIoVolume(RamVolume): def __init__(self, *args, **kwargs): @@ -852,8 +851,7 @@ def resume_next(self): cvol.destroy() -@pytest.mark.parametrize("rand_seed", [datetime.now().timestamp()]) -def test_io_error(pyocf_ctx, rand_seed): +def test_io_error(pyocf_ctx): """ title: Composite volume error propagation. description: | @@ -876,7 +874,7 @@ def test_io_error(pyocf_ctx, rand_seed): requirements: - composite_volume::io_error_handling """ - random.seed(rand_seed) + random.seed(get_random_seed()) pyocf_ctx.register_volume_type(TraceDevice) vol_size = S.from_MiB(1) diff --git a/tests/functional/tests/utils/random.py b/tests/functional/tests/utils/random.py index 0ec22609e..7d9d8b5b1 100644 --- a/tests/functional/tests/utils/random.py +++ b/tests/functional/tests/utils/random.py @@ -1,5 +1,6 @@ # # Copyright(c) 2019-2023 Intel Corporation +# Copyright(c) 2026 Unvertical # SPDX-License-Identifier: BSD-3-Clause # @@ -10,6 +11,11 @@ from ctypes import c_uint64, c_uint32, c_uint16, c_uint8, c_int, c_uint +def get_random_seed(): + with open("config/random.cfg") as f: + return int(f.read()) + + class Range: def __init__(self, min_val, max_val): self.min = min_val @@ -30,8 +36,7 @@ class DefaultRanges(Range, enum.Enum): class RandomGenerator: def __init__(self, base_range=DefaultRanges.INT, count=1000): - with open("config/random.cfg") as f: - self.random = random.Random(int(f.read())) + self.random = random.Random(get_random_seed()) self.exclude = [] self.range = base_range self.count = count @@ -60,8 +65,7 @@ def __next__(self): class RandomStringGenerator: def __init__(self, len_range=Range(0, 20), count=700, extra_chars=[]): - with open("config/random.cfg") as f: - self.random = random.Random(int(f.read())) + self.random = random.Random(get_random_seed()) self.generator = self.__string_generator(len_range, extra_chars) self.count = count self.n = 0 From 4f9c9c6805abe328e8f2a447f77b732271bcbb98 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Sat, 14 Mar 2026 17:17:08 +0100 Subject: [PATCH 2/4] pyocf: Don't check for opened volumes if test failed Assertions stop the test execution, so we should expect some volumes to be left in opened state. This prevents noisy warning. Co-developed-by: Claude Opus 4.6 Signed-off-by: Claude Opus 4.6 Signed-off-by: Robert Baldyga --- tests/functional/tests/conftest.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/functional/tests/conftest.py b/tests/functional/tests/conftest.py index 4667bd5ee..94920a3ee 100644 --- a/tests/functional/tests/conftest.py +++ b/tests/functional/tests/conftest.py @@ -1,5 +1,6 @@ # # Copyright(c) 2019-2022 Intel Corporation +# Copyright(c) 2026 Unvertical # SPDX-License-Identifier: BSD-3-Clause # @@ -27,8 +28,16 @@ def pytest_configure(config): sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir)) +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item, call): + outcome = yield + rep = outcome.get_result() + if rep.when == "call": + item.test_failed = rep.failed + + @pytest.fixture() -def pyocf_ctx(): +def pyocf_ctx(request): c = OcfCtx.with_defaults(DefaultLogger(LogLevel.WARN)) for vol_type in default_registered_volumes: c.register_volume_type(vol_type) @@ -36,12 +45,14 @@ def pyocf_ctx(): yield c c.exit() gc.collect() + if getattr(request.node, "test_failed", False): + return if len(Volume._instances_) > 0: warnings.warn("Not all Volumes have been closed!!!") @pytest.fixture() -def pyocf_ctx_log_buffer(): +def pyocf_ctx_log_buffer(request): logger = BufferLogger(LogLevel.DEBUG) c = OcfCtx.with_defaults(logger) for vol_type in default_registered_volumes: @@ -50,12 +61,14 @@ def pyocf_ctx_log_buffer(): yield logger c.exit() gc.collect() + if getattr(request.node, "test_failed", False): + return if len(Volume._instances_) > 0: warnings.warn("Not all Volumes have been closed!!!") @pytest.fixture() -def pyocf_2_ctx(): +def pyocf_2_ctx(request): c1 = OcfCtx.with_defaults(DefaultLogger(LogLevel.WARN, "Ctx1")) c2 = OcfCtx.with_defaults(DefaultLogger(LogLevel.WARN, "Ctx2")) for vol_type in default_registered_volumes: @@ -67,12 +80,14 @@ def pyocf_2_ctx(): c1.exit() c2.exit() gc.collect() + if getattr(request.node, "test_failed", False): + return if len(Volume._instances_) > 0: warnings.warn("Not all Volumes have been closed!!!") @pytest.fixture() -def pyocf_2_ctx_log_buffer(): +def pyocf_2_ctx_log_buffer(request): logger1 = BufferLogger(LogLevel.WARN, LogLevel.DEBUG, "Ctx1") logger2 = BufferLogger(LogLevel.WARN, LogLevel.DEBUG, "Ctx2") c1 = OcfCtx.with_defaults(logger1) @@ -84,5 +99,7 @@ def pyocf_2_ctx_log_buffer(): c1.exit() c2.exit() gc.collect() + if getattr(request.node, "test_failed", False): + return if len(Volume._instances_) > 0: warnings.warn("Not all Volumes have been closed!!!") From bb0aae74365e66afbd3955db7aeb1c392ddebfc7 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Sat, 14 Mar 2026 17:34:03 +0100 Subject: [PATCH 3/4] pyocf: Do not attempt joining thread from itself Co-developed-by: Claude Opus 4.6 Signed-off-by: Claude Opus 4.6 Signed-off-by: Robert Baldyga --- tests/functional/pyocf/types/queue.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/functional/pyocf/types/queue.py b/tests/functional/pyocf/types/queue.py index 7218000b4..91ecf6f26 100644 --- a/tests/functional/pyocf/types/queue.py +++ b/tests/functional/pyocf/types/queue.py @@ -1,11 +1,12 @@ # # Copyright(c) 2019-2022 Intel Corporation # Copyright(c) 2024 Huawei Technologies +# Copyright(c) 2026 Unvertical # SPDX-License-Identifier: BSD-3-Clause # from ctypes import c_void_p, CFUNCTYPE, Structure, byref -from threading import Thread, Condition, Event, Semaphore +from threading import Thread, Condition, Event, Semaphore, current_thread import weakref from ..ocf import OcfLib @@ -113,7 +114,8 @@ def stop(self): self.stop_event.set() self.kick_condition.notify_all() - self.thread.join() + if current_thread() is not self.thread: + self.thread.join() # settle - wait for OCF to finish execution within this queue context # From 7b6194cfff8595fbbedcea651824f14a96918f1f Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Sat, 14 Mar 2026 17:17:55 +0100 Subject: [PATCH 4/4] pyocf: Add missing settle() Wait for the I/Os to complete before checking stats. Co-developed-by: Claude Opus 4.6 Signed-off-by: Claude Opus 4.6 Signed-off-by: Robert Baldyga --- tests/functional/tests/engine/test_seq_cutoff.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/functional/tests/engine/test_seq_cutoff.py b/tests/functional/tests/engine/test_seq_cutoff.py index 8b60280ae..e92d046f0 100644 --- a/tests/functional/tests/engine/test_seq_cutoff.py +++ b/tests/functional/tests/engine/test_seq_cutoff.py @@ -115,6 +115,7 @@ def test_seq_cutoff_max_streams(pyocf_ctx): io_size = threshold - smallest_io_size io_to_streams(vol, queue, streams, io_size) + queue.settle() stats = cache.get_stats() assert ( stats["req"]["serviced"]["value"] == stats["req"]["total"]["value"] == len(streams) @@ -129,6 +130,7 @@ def test_seq_cutoff_max_streams(pyocf_ctx): shuffle(streams) io_to_streams(vol, queue, streams, smallest_io_size) + queue.settle() stats = cache.get_stats() assert ( stats["req"]["serviced"]["value"] == old_serviced @@ -140,6 +142,7 @@ def test_seq_cutoff_max_streams(pyocf_ctx): # STEP 3 io_to_streams(vol, queue, [non_active_stream], smallest_io_size) + queue.settle() stats = cache.get_stats() assert ( stats["req"]["serviced"]["value"] == old_serviced + 1 @@ -149,6 +152,7 @@ def test_seq_cutoff_max_streams(pyocf_ctx): io_to_streams(vol, queue, [lru_stream], smallest_io_size) vol.close() + queue.settle() stats = cache.get_stats() assert (