Functional tests for WO cache mode
Signed-off-by: Adam Rutkowski <adam.j.rutkowski@intel.com>
This commit is contained in:
parent
b97bb6f53b
commit
641fba1708
@ -96,10 +96,12 @@ class Data:
|
|||||||
return cls(pages * Data.PAGE_SIZE)
|
return cls(pages * Data.PAGE_SIZE)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bytes(cls, source: bytes):
|
def from_bytes(cls, source: bytes, offset: int = 0, size: int = 0):
|
||||||
d = cls(len(source))
|
if size == 0:
|
||||||
|
size = len(source) - offset
|
||||||
|
d = cls(size)
|
||||||
|
|
||||||
memmove(d.handle, cast(source, c_void_p), len(source))
|
memmove(d.handle, cast(source, c_void_p).value + offset, size)
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@ -108,9 +108,9 @@ class Io(Structure):
|
|||||||
byref(self), addr, length, direction, io_class, flags
|
byref(self), addr, length, direction, io_class, flags
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_data(self, data: Data):
|
def set_data(self, data: Data, offset: int = 0):
|
||||||
self.data = data
|
self.data = data
|
||||||
OcfLib.getInstance().ocf_io_set_data_wrapper(byref(self), data, 0)
|
OcfLib.getInstance().ocf_io_set_data_wrapper(byref(self), data, offset)
|
||||||
|
|
||||||
def set_queue(self, queue: Queue):
|
def set_queue(self, queue: Queue):
|
||||||
OcfLib.getInstance().ocf_io_set_queue_wrapper(byref(self), queue.handle)
|
OcfLib.getInstance().ocf_io_set_queue_wrapper(byref(self), queue.handle)
|
||||||
|
187
tests/functional/tests/engine/test_wo.py
Normal file
187
tests/functional/tests/engine/test_wo.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
#
|
||||||
|
# Copyright(c) 2019 Intel Corporation
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause-Clear
|
||||||
|
#
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ctypes import c_int, memmove, cast, c_void_p
|
||||||
|
from enum import IntEnum
|
||||||
|
from itertools import product
|
||||||
|
import random
|
||||||
|
|
||||||
|
from pyocf.types.cache import Cache, CacheMode
|
||||||
|
from pyocf.types.core import Core
|
||||||
|
from pyocf.types.volume import Volume, ErrorDevice
|
||||||
|
from pyocf.types.data import Data
|
||||||
|
from pyocf.types.io import IoDir
|
||||||
|
from pyocf.utils import Size
|
||||||
|
from pyocf.types.shared import OcfError, OcfCompletion
|
||||||
|
|
||||||
|
def __io(io, queue, address, size, data, direction):
|
||||||
|
io.set_data(data, 0)
|
||||||
|
io.configure(address, size, direction, 0, 0)
|
||||||
|
io.set_queue(queue)
|
||||||
|
completion = OcfCompletion([("err", c_int)])
|
||||||
|
io.callback = completion.callback
|
||||||
|
io.submit()
|
||||||
|
completion.wait()
|
||||||
|
return int(completion.results['err'])
|
||||||
|
|
||||||
|
|
||||||
|
def _io(io, queue, address, size, data, offset, direction):
|
||||||
|
if direction == IoDir.READ:
|
||||||
|
_data = Data.from_bytes(bytes(size))
|
||||||
|
else:
|
||||||
|
_data = Data.from_bytes(data, offset, size)
|
||||||
|
ret = __io(io, queue, address, size, _data, direction)
|
||||||
|
if not ret and direction == IoDir.READ:
|
||||||
|
memmove(cast(data, c_void_p).value + offset, _data.handle, size)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def io_to_core(core, address, size, data, offset, direction):
|
||||||
|
return _io(core.new_core_io(), core.cache.get_default_queue(), address, size,
|
||||||
|
data, offset, direction)
|
||||||
|
|
||||||
|
def io_to_exp_obj(core, address, size, data, offset, direction):
|
||||||
|
return _io(core.new_io(), core.cache.get_default_queue(), address, size, data,
|
||||||
|
offset, direction)
|
||||||
|
|
||||||
|
def sector_to_region(sector, region_start):
|
||||||
|
i = 0
|
||||||
|
while i < len(region_start) - 1 and sector >= region_start[i + 1]:
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
class SectorStatus(IntEnum):
|
||||||
|
DIRTY = 0,
|
||||||
|
CLEAN = 1,
|
||||||
|
INVALID = 2,
|
||||||
|
|
||||||
|
I = SectorStatus.INVALID
|
||||||
|
D = SectorStatus.DIRTY
|
||||||
|
C = SectorStatus.CLEAN
|
||||||
|
|
||||||
|
# Test reads with 4k cacheline and different combinations of sectors status and
|
||||||
|
# IO range. Three consecutive core lines are targeted, with the middle one (no 1)
|
||||||
|
# having all sectors status (clean, dirty, invalid) set independently. The other
|
||||||
|
# two lines either are fully dirty/clean/invalid or have the single sector
|
||||||
|
# neighbouring with middle core line with different status. This gives total of
|
||||||
|
# 12 regions with independent state, listed on the diagram below.
|
||||||
|
#
|
||||||
|
# cache line | CL 0 | CL 1 | CL 2 |
|
||||||
|
# sector no |01234567|89ABCDEF|(ctd..) |
|
||||||
|
# |........|........|........|
|
||||||
|
# region no |00000001|23456789|ABBBBBBB|
|
||||||
|
# io start possible | | | |
|
||||||
|
# values @START |> >>|>>>>>>>>| |
|
||||||
|
# io end possible | | | |
|
||||||
|
# values @END | |<<<<<<<<|<< <|
|
||||||
|
#
|
||||||
|
# Each test iteration is described by region states and IO start/end sectors,
|
||||||
|
# giving total of 14 parameters
|
||||||
|
#
|
||||||
|
# In order to determine data consistency, cache is filled with data so so that:
|
||||||
|
# - core sector no @n is filled with @n
|
||||||
|
# - if clean, exported object sector no @n is filled with 100 + @n
|
||||||
|
# - if dirty, exported object sector no @n is filled with 200 + @n
|
||||||
|
#
|
||||||
|
def test_wo_read_data_consistency(pyocf_ctx):
|
||||||
|
# start sector for each region
|
||||||
|
region_start = [0, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
|
||||||
|
# possible start sectors for test iteration
|
||||||
|
start_sec = [0, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
||||||
|
# possible end sectors for test iteration
|
||||||
|
end_sec = [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 23]
|
||||||
|
|
||||||
|
SECTOR_SIZE = 512
|
||||||
|
CACHELINE_SIZE = 4096
|
||||||
|
WORKSET_SIZE = 3 * CACHELINE_SIZE
|
||||||
|
WORKSET_OFFSET = 1024 * CACHELINE_SIZE
|
||||||
|
SECTOR_COUNT = int(WORKSET_SIZE / SECTOR_SIZE)
|
||||||
|
ITRATION_COUNT = 200
|
||||||
|
|
||||||
|
# fixed test cases
|
||||||
|
fixed_combinations = [
|
||||||
|
[I, I, D, D, D, D, D, D, D, D, I, I],
|
||||||
|
[I, I, C, C, C, C, C, C, C, C, I, I],
|
||||||
|
[I, I, D, D, D, I, D, D, D, D, I, I],
|
||||||
|
[I, I, D, D, D, I, I, D, D, D, I, I],
|
||||||
|
[I, I, I, I, D, I, I, D, C, D, I, I],
|
||||||
|
[I, D, D, D, D, D, D, D, D, D, D, I],
|
||||||
|
[C, C, I, D, D, I, D, D, D, D, D, I],
|
||||||
|
[D, D, D, D, D, D, D, D, D, D, D, I],
|
||||||
|
]
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
# memset n-th sector of core data with n
|
||||||
|
data[SectorStatus.INVALID] = bytes([x // SECTOR_SIZE for x in range (WORKSET_SIZE)])
|
||||||
|
# memset n-th sector of clean data with n + 100
|
||||||
|
data[SectorStatus.CLEAN] = bytes([100 + x // SECTOR_SIZE for x in range (WORKSET_SIZE)])
|
||||||
|
# memset n-th sector of dirty data with n + 200
|
||||||
|
data[SectorStatus.DIRTY] = bytes([200 + x // SECTOR_SIZE for x in range (WORKSET_SIZE)])
|
||||||
|
|
||||||
|
result_b = bytes(WORKSET_SIZE)
|
||||||
|
|
||||||
|
cache_device = Volume(Size.from_MiB(30))
|
||||||
|
core_device = Volume(Size.from_MiB(30))
|
||||||
|
|
||||||
|
cache = Cache.start_on_device(cache_device, cache_mode=CacheMode.WO)
|
||||||
|
core = Core.using_device(core_device)
|
||||||
|
|
||||||
|
cache.add_core(core)
|
||||||
|
|
||||||
|
# generate regions status combinations and shuffle it
|
||||||
|
combinations = []
|
||||||
|
state_combinations = product(SectorStatus, repeat=len(region_start))
|
||||||
|
for S in state_combinations:
|
||||||
|
combinations.append(S)
|
||||||
|
random.shuffle(combinations)
|
||||||
|
|
||||||
|
# add fixed test cases at the beginnning
|
||||||
|
combinations = fixed_combinations + combinations
|
||||||
|
|
||||||
|
for S in combinations[:ITRATION_COUNT]:
|
||||||
|
# write data to core and invalidate all CL
|
||||||
|
cache.change_cache_mode(cache_mode = CacheMode.PT)
|
||||||
|
io_to_exp_obj(core, WORKSET_OFFSET, len(data[SectorStatus.INVALID]), \
|
||||||
|
data[SectorStatus.INVALID], 0, IoDir.WRITE)
|
||||||
|
|
||||||
|
# insert clean sectors
|
||||||
|
cache.change_cache_mode(cache_mode = CacheMode.WT)
|
||||||
|
for sec in range(SECTOR_COUNT):
|
||||||
|
region = sector_to_region(sec, region_start)
|
||||||
|
if S[region] == SectorStatus.CLEAN:
|
||||||
|
io_to_exp_obj(core, WORKSET_OFFSET + SECTOR_SIZE * sec, SECTOR_SIZE, \
|
||||||
|
data[SectorStatus.CLEAN], sec * SECTOR_SIZE, IoDir.WRITE)
|
||||||
|
|
||||||
|
# write dirty sectors
|
||||||
|
cache.change_cache_mode(cache_mode = CacheMode.WO)
|
||||||
|
for sec in range(SECTOR_COUNT):
|
||||||
|
region = sector_to_region(sec, region_start)
|
||||||
|
if S[region] == SectorStatus.DIRTY:
|
||||||
|
io_to_exp_obj(core, WORKSET_OFFSET + SECTOR_SIZE * sec, SECTOR_SIZE, \
|
||||||
|
data[SectorStatus.DIRTY], sec * SECTOR_SIZE, IoDir.WRITE)
|
||||||
|
|
||||||
|
for s in start_sec:
|
||||||
|
for e in end_sec:
|
||||||
|
if s > e:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# issue WO read
|
||||||
|
START = s * SECTOR_SIZE
|
||||||
|
END = e * SECTOR_SIZE
|
||||||
|
size = (e - s + 1) * SECTOR_SIZE
|
||||||
|
assert(0 == io_to_exp_obj(core, WORKSET_OFFSET + START, size, \
|
||||||
|
result_b, START, IoDir.READ)), \
|
||||||
|
"error reading in WO mode: S={}, start={}, end={}".format( \
|
||||||
|
S, s, e)
|
||||||
|
|
||||||
|
# verify read data
|
||||||
|
for sec in range(s, e + 1):
|
||||||
|
# just check the first byte of sector
|
||||||
|
region = sector_to_region(sec, region_start)
|
||||||
|
check_byte = sec * SECTOR_SIZE
|
||||||
|
assert(result_b[check_byte] == data[S[region]][check_byte]), \
|
||||||
|
"unexpected data in sector {}, S={}, s={}, e={}\n".format( \
|
||||||
|
sec, S, s, e)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user