Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
714 changes: 22 additions & 692 deletions env/posix/ocf_env.h

Large diffs are not rendered by default.

770 changes: 770 additions & 0 deletions env/posix/ocf_env_default.h

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions tests/functional/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#
# Copyright(c) 2019-2022 Intel Corporation
# Copyright(c) 2025 Huawei Technologies
# Copyright(c) 2026 Unvertical
# SPDX-License-Identifier: BSD-3-Clause
#

Expand All @@ -11,6 +12,7 @@ SRCDIR=$(ADAPTERDIR)/ocf/src
INCDIR=$(ADAPTERDIR)/ocf/include
WRAPDIR=$(ADAPTERDIR)/c/wrappers
HELPDIR=$(ADAPTERDIR)/c/helpers
ENVDIR=$(ADAPTERDIR)/c/env

CFLAGS=-g -Wall -I$(INCDIR) -I$(SRCDIR)/ocf/env $(OPT_CFLAGS)
LDFLAGS=-pthread #-lz
Expand All @@ -19,9 +21,14 @@ SRC=$(shell find $(SRCDIR) $(WRAPDIR) $(HELPDIR) -name \*.c)
OBJS=$(patsubst %.c, %.o, $(SRC))
OCFLIB=$(ADAPTERDIR)/libocf.so

all: | sync config_random
all: | sync env_override config_random
$(MAKE) $(OCFLIB)

env_override: sync
@rm -f $(SRCDIR)/ocf/env/ocf_env.h
@cp $(ENVDIR)/ocf_env.h $(SRCDIR)/ocf/env/ocf_env.h
@cp $(ENVDIR)/ocf_env_time.h $(SRCDIR)/ocf/env/ocf_env_time.h

$(OCFLIB): $(OBJS)
@echo "Building $@"
@$(CC) -coverage -shared -o $@ $(CFLAGS) $^ -fPIC $(LDFLAGS)
Expand Down Expand Up @@ -51,4 +58,4 @@ distclean: clean
@find . -name *.gc* -delete
@echo " DISTCLEAN "

.PHONY: all clean sync config_random distclean
.PHONY: all clean sync env_override config_random distclean
37 changes: 37 additions & 0 deletions tests/functional/pyocf/c/env/ocf_env.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright(c) 2026 Unvertical
* SPDX-License-Identifier: BSD-3-Clause
*/

/*
* Custom ocf_env.h for pyocf test environment.
*
* Replaces OCF_ENV_POSIX_TIME with a custom implementation that supports
* controllable time offset for testing.
*/

#ifndef __OCF_ENV_H__
#define __OCF_ENV_H__

#define OCF_ENV_POSIX_DEBUG
#define OCF_ENV_POSIX_STRING
#define OCF_ENV_POSIX_MEMORY
#define OCF_ENV_POSIX_MUTEX
#define OCF_ENV_POSIX_RMUTEX
#define OCF_ENV_POSIX_RWSEM
#define OCF_ENV_POSIX_COMPLETION
#define OCF_ENV_POSIX_ATOMIC
#define OCF_ENV_POSIX_SPINLOCK
#define OCF_ENV_POSIX_RWLOCK
#define OCF_ENV_POSIX_BIT
#define OCF_ENV_POSIX_SCHEDULING
/* OCF_ENV_POSIX_TIME intentionally not defined - using custom impl */
#define OCF_ENV_POSIX_SORTING
#define OCF_ENV_POSIX_CRC
#define OCF_ENV_POSIX_EXECUTION_CONTEXT

#include "ocf_env_default.h"

#include "ocf_env_time.h"

#endif /* __OCF_ENV_H__ */
66 changes: 66 additions & 0 deletions tests/functional/pyocf/c/env/ocf_env_time.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright(c) 2026 Unvertical
* SPDX-License-Identifier: BSD-3-Clause
*/

/*
* Custom TIME section for pyocf test environment.
*
* Replaces the default posix env_get_tick_count with a fully test-controlled
* counter. The "current time" is solely the value of ocf_env_tick_count_offset,
* which tests advance explicitly. This makes time deterministic for cleaner
* tests and avoids real-time drift between OCF time queries within a single
* test.
*
* All other time functions (conversions, sleep) remain identical to the default
* posix implementation.
*/

#ifndef __OCF_ENV_TIME_H__
#define __OCF_ENV_TIME_H__

#include <stdint.h>
#include <time.h>
#include <unistd.h>

#define ENV_SEC_TO_NSEC(_sec) ((_sec) * 1000000000)
#define ENV_NSEC_TO_SEC(_sec) ((_sec) / 1000000000)
#define ENV_NSEC_TO_MSEC(_sec) ((_sec) / 1000000)

extern uint64_t ocf_env_tick_count_offset;

static inline uint64_t env_get_tick_count(void)
{
return ocf_env_tick_count_offset;
}

static inline uint64_t env_ticks_to_nsecs(uint64_t j)
{
return j;
}

static inline uint64_t env_ticks_to_msecs(uint64_t j)
{
return ENV_NSEC_TO_MSEC(j);
}

static inline uint64_t env_ticks_to_secs(uint64_t j)
{
return ENV_NSEC_TO_SEC(j);
}

static inline uint64_t env_secs_to_ticks(uint64_t j)
{
return ENV_SEC_TO_NSEC(j);
}

static inline void env_msleep(uint64_t n)
{
usleep(n * 1000);
}

struct env_timeval {
uint64_t sec, usec;
};

#endif /* __OCF_ENV_TIME_H__ */
2 changes: 2 additions & 0 deletions tests/functional/pyocf/c/helpers/ocf_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include "ocf/ocf.h"
#include "../src/ocf/ocf_def_priv.h"

uint64_t ocf_env_tick_count_offset = 0;

bool ocf_is_block_size_4k(void)
{
#ifdef OCF_BLOCK_SIZE_4K
Expand Down
28 changes: 28 additions & 0 deletions tests/functional/pyocf/time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#
# Copyright(c) 2026 Unvertical
# SPDX-License-Identifier: BSD-3-Clause
#

from ctypes import c_uint64

from .ocf import OcfLib

SEC_IN_NS = 1_000_000_000


def advance_time(secs):
lib = OcfLib.getInstance()
offset = c_uint64.in_dll(lib, "ocf_env_tick_count_offset")
offset.value += secs * SEC_IN_NS


def advance_time_ms(ms):
lib = OcfLib.getInstance()
offset = c_uint64.in_dll(lib, "ocf_env_tick_count_offset")
offset.value += ms * 1_000_000


def reset_time():
lib = OcfLib.getInstance()
offset = c_uint64.in_dll(lib, "ocf_env_tick_count_offset")
offset.value = 0
141 changes: 138 additions & 3 deletions tests/functional/pyocf/types/cleaner.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#
# Copyright(c) 2019-2021 Intel Corporation
# Copyright(c) 2026 Unvertical
# SPDX-License-Identifier: BSD-3-Clause
#

from ctypes import c_void_p, CFUNCTYPE, Structure, c_int
from ctypes import c_void_p, c_uint32, c_int, CFUNCTYPE, Structure
from threading import Thread, Timer, Event
from .shared import SharedOcfObject
from ..ocf import OcfLib


class CleanerOps(Structure):
Expand All @@ -15,9 +18,25 @@ class CleanerOps(Structure):
_fields_ = [("init", INIT), ("kick", KICK), ("stop", STOP)]


CLEANER_END = CFUNCTYPE(None, c_void_p, c_uint32)


class _CleanerState:
def __init__(self):
self.kick_event = Event()
self.stop_event = Event()
self.thread = None
self.timer = None
self.queue = None
self.last_interval = 0


class Cleaner(SharedOcfObject):
_instances_ = {}
_fields_ = [("cleaner", c_void_p)]
_cleaners = {}
_end_handler = None
_kick_handler = None

def __init__(self):
self._as_parameter_ = self.cleaner
Expand All @@ -27,17 +46,133 @@ def __init__(self):
def get_ops(cls):
return CleanerOps(init=cls._init, kick=cls._kick, stop=cls._stop)

@classmethod
def set_end_handler(cls, handler):
"""Override the default end handler.

The handler receives (cleaner, interval) and is responsible for
scheduling the next cleaner iteration. Set to None to restore
the default behaviour (wait the interval before re-kicking).
"""
cls._end_handler = handler

@classmethod
def set_kick_handler(cls, handler):
"""Override the default kick handler.

The handler receives (cleaner) and decides whether to schedule a
cleaner iteration. Set to None to restore the default behaviour
(kick the cleaner thread to run an iteration).
"""
cls._kick_handler = handler

@staticmethod
def _resolve_queue(cleaner):
from pyocf.types.ctx import OcfCtx

cache_handle = lib.ocf_cleaner_get_cache(cleaner)

ctx = OcfCtx.get_default()
if ctx is None:
return None

for c in ctx.caches:
if c.cache_handle.value == cache_handle and c.io_queues:
return c.io_queues[0]

return None

@staticmethod
def _cleaner_thread(cleaner):
state = Cleaner._cleaners.get(cleaner)
if not state:
return

while not state.stop_event.is_set():
state.kick_event.wait()
if state.stop_event.is_set():
break
state.kick_event.clear()
if state.queue:
lib.ocf_cleaner_run(cleaner, state.queue.handle)

@staticmethod
def _default_end(cleaner, interval):
state = Cleaner._cleaners.get(cleaner)
if state is None or state.stop_event.is_set():
return
state.last_interval = interval
if interval > 0:
state.timer = Timer(
interval / 1000.0, state.kick_event.set
)
state.timer.daemon = True
state.timer.start()
else:
state.kick_event.set()

@staticmethod
@CleanerOps.INIT
def _init(cleaner):
lib.ocf_cleaner_set_cmpl(cleaner, Cleaner._end)

state = _CleanerState()
state.queue = Cleaner._resolve_queue(cleaner)
Cleaner._cleaners[cleaner] = state

state.thread = Thread(
target=Cleaner._cleaner_thread,
args=(cleaner,),
daemon=True,
name="cleaner",
)
state.thread.start()
return 0

@staticmethod
def _default_kick(cleaner):
state = Cleaner._cleaners.get(cleaner)
if state is None:
return
if state.queue is None:
state.queue = Cleaner._resolve_queue(cleaner)
state.kick_event.set()

@staticmethod
@CleanerOps.KICK
def _kick(cleaner):
pass
handler = Cleaner._kick_handler
if handler is not None:
handler(cleaner)
else:
Cleaner._default_kick(cleaner)

@staticmethod
@CLEANER_END
def _end(cleaner, interval):
handler = Cleaner._end_handler
if handler is not None:
handler(cleaner, interval)
else:
Cleaner._default_end(cleaner, interval)

@staticmethod
@CleanerOps.STOP
def _stop(cleaner):
pass
state = Cleaner._cleaners.pop(cleaner, None)
if state is None:
return
state.stop_event.set()
if state.timer:
state.timer.cancel()
state.kick_event.set()
if state.thread and state.thread.is_alive():
state.thread.join(timeout=5)


lib = OcfLib.getInstance()
lib.ocf_cleaner_set_cmpl.argtypes = [c_void_p, CLEANER_END]
lib.ocf_cleaner_get_cache.argtypes = [c_void_p]
lib.ocf_cleaner_get_cache.restype = c_void_p
lib.ocf_cleaner_run.argtypes = [c_void_p, c_void_p]
lib.ocf_kick_cleaner.argtypes = [c_void_p]
4 changes: 4 additions & 0 deletions tests/functional/pyocf/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,13 @@ def __mul__(self, other):
return Size(self.bytes * int(other))

def __truediv__(self, other):
if isinstance(other, Size):
return self.bytes / other.bytes
return Size(self.bytes / int(other))

def __floordiv__(self, other):
if isinstance(other, Size):
return self.bytes // other.bytes
return Size(self.bytes // int(other))

def __rmul__(self, other):
Expand Down
4 changes: 4 additions & 0 deletions tests/functional/tests/cleaning/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#
# Copyright(c) 2026 Unvertical
# SPDX-License-Identifier: BSD-3-Clause
#
Loading
Loading