Commit e1c96b1d authored by Kirill Smelkov's avatar Kirill Smelkov

wcfs: tests: Use bytestrings uniformly

WCFS tests follow bytestring model from the beginning because it
operates with binary data and binary messages to/from WCFS server. It
all works ok on py2, but running tests on py3 yielded several problems.
For example str and bytes were mixed in the innermost part of _assertBlk

        def _(ctx, ev):
            assert t.cached()[blk] == cached
            ev.append('read pre')

            # access data with released GIL so that the thread that reads data from
            # head/watch can receive pin message. Be careful to handle cancellation,
            # so that on error in another worker we don't get stuck and the
            # error can be propagated to wait and reported.
            #
            # we handle cancellation by spawning read in another thread and
            # waiting for either ctx cancel, or read thread to complete. This
            # way on ctx cancel (e.g. assertion failure in another worker), the
            # read thread can remain running even after _assertBlk returns, and
            # in particular till the point where the whole test is marked as
            # failed and shut down. But on test shutdown .fmmap is unmapped for
            # all opened tFiles, and so read will hit SIGSEGV. Prepare to catch
            # that SIGSEGV here.
            have_read = chan(1)
            def _():
                try:
                    b = read_exfault_nogil(blkview[0:1])
                except SegmentationFault:
                    b = 'FAULT'
                t._blkaccess(blk)
                have_read.send(b)
            go(_)
            _, _rx = select(
                ctx.done().recv,    # 0
                have_read.recv,     # 1
            )
            if _ == 0:
                raise ctx.err()
            b = _rx

    >       ev.append('read ' + b)
    E       TypeError: can only concatenate str (not "bytes") to str

bytes input was split with str delimiter

    def _loadStats(t): # -> {}
        stats = {}
        for l in t.wc._read(".wcfs/stats").splitlines():
            # key : value
>           k, v = l.split(':')
E           TypeError: a bytes-like object is required, not 'str'

and str object rejected when assigning to C `char*`:

    Traceback (most recent call last):
      File "golang/_golang.pyx", line 156, in golang._golang.__goviac
      File "wcfs/internal/wcfs_test.pyx", line 58, in wendelin.wcfs.internal.wcfs_test._tWCFS._abort_ontimeout
    TypeError: expected bytes, str found

-> Fix all those overlooks by consistently using bytestrings everywhere.

On py3 the implementation depends on nexedi/pygolang!21,
but on py2 it works both with and without pygolang bstr patches.

Preliminary history:

    vnmabus/wendelin.core@af56bd31

but it takes the reverse approach and mixes in pytest.approx for
timestamp assert which is unrelated to the topic and is not correct as
default pytest.approx behaviour is to use 1e-6 relative precision which
results in ~1700s seconds tolerance instead of intended 1µs:

    In [1]: import pytest
    In [2]: import time

    In [3]: t = time.time()

    In [4]: t
    Out[4]: 1727271692.0636573

    In [5]: t == t
    Out[5]: True

    In [6]: t == pytest.approx(t+1000)
    Out[6]: True

    In [7]: t == pytest.approx(t+2000)
    Out[7]: False

So using pytest.approx resulted in tests to become accepting faulty wcfs
behaviour.

Timestamp assert will be handled properly in the next patch.
Co-authored-by: Carlos Ramos Carreño's avatarCarlos Ramos Carreño <carlos.ramos@nexedi.com>
parent 69aab23a
...@@ -54,7 +54,7 @@ cdef class _tWCFS: ...@@ -54,7 +54,7 @@ cdef class _tWCFS:
# but, if _abort_ontimeout uses GIL, won't continue to run trying to lock # but, if _abort_ontimeout uses GIL, won't continue to run trying to lock
# GIL -> deadlock. # GIL -> deadlock.
def _abort_ontimeout(_tWCFS t, int fdabort, double dt, pychan timeoutch not None, pychan nogilready not None): def _abort_ontimeout(_tWCFS t, int fdabort, double dt, pychan timeoutch not None, pychan nogilready not None):
emsg1 = "\nC: test timed out after %.1fs\n" % (dt / time.second) emsg1 = b"\nC: test timed out after %.1fs\n" % (dt / time.second)
cdef char *_emsg1 = emsg1 cdef char *_emsg1 = emsg1
with nogil: with nogil:
# tell main thread that we entered nogil world # tell main thread that we entered nogil world
......
...@@ -58,6 +58,8 @@ from wendelin.wcfs.internal.wcfs_test import _tWCFS, read_exfault_nogil, Segment ...@@ -58,6 +58,8 @@ from wendelin.wcfs.internal.wcfs_test import _tWCFS, read_exfault_nogil, Segment
from wendelin.wcfs.client._wcfs import _tpywlinkwrite as _twlinkwrite from wendelin.wcfs.client._wcfs import _tpywlinkwrite as _twlinkwrite
from wendelin.wcfs import _is_mountpoint as is_mountpoint, _procwait as procwait, _waitfor as waitfor, _ready as ready, _rmdir_ifexists as rmdir_ifexists from wendelin.wcfs import _is_mountpoint as is_mountpoint, _procwait as procwait, _waitfor as waitfor, _ready as ready, _rmdir_ifexists as rmdir_ifexists
bstr = type(b('')) # TODO import directly after https://lab.nexedi.com/nexedi/pygolang/-/merge_requests/21 is merged
# setup: # setup:
# - create test database, compute zurl and mountpoint for wcfs # - create test database, compute zurl and mountpoint for wcfs
...@@ -466,14 +468,14 @@ class tWCFS(_tWCFS): ...@@ -466,14 +468,14 @@ class tWCFS(_tWCFS):
assert kv == kvok, "stats did not stay at expected state" assert kv == kvok, "stats did not stay at expected state"
# _loadStats loads content of .wcfs/stats . # _loadStats loads content of .wcfs/stats .
def _loadStats(t): # -> {} def _loadStats(t): # -> {} bstr -> int
stats = {} stats = {}
for l in t.wc._read(".wcfs/stats").splitlines(): for l in t.wc._read(".wcfs/stats").splitlines():
# key : value # key : value
k, v = l.split(':') k, v = l.split(b':')
k = k.strip() k = k.strip()
v = v.strip() v = v.strip()
stats[k] = int(v) stats[b(k)] = int(v)
# verify that keys remains the same and that cumulative counters do not decrease # verify that keys remains the same and that cumulative counters do not decrease
if t._stats_prev is not None: if t._stats_prev is not None:
...@@ -824,6 +826,7 @@ class tFile: ...@@ -824,6 +826,7 @@ class tFile:
@func @func
def _assertBlk(t, blk, dataok, pinokByWLink=None, pinfunc=None, timeout=None): def _assertBlk(t, blk, dataok, pinokByWLink=None, pinfunc=None, timeout=None):
assert isinstance(dataok, bstr)
assert len(dataok) <= t.blksize assert len(dataok) <= t.blksize
dataok += b'\0'*(t.blksize - len(dataok)) # tailing zeros dataok += b'\0'*(t.blksize - len(dataok)) # tailing zeros
assert blk < t._sizeinblk() assert blk < t._sizeinblk()
...@@ -897,11 +900,11 @@ class tFile: ...@@ -897,11 +900,11 @@ class tFile:
have_read = chan(1) have_read = chan(1)
def _(): def _():
try: try:
b = read_exfault_nogil(blkview[0:1]) got = read_exfault_nogil(blkview[0:1])
except SegmentationFault: except SegmentationFault:
b = 'FAULT' got = 'FAULT'
t._blkaccess(blk) t._blkaccess(blk)
have_read.send(b) have_read.send(got)
go(_) go(_)
_, _rx = select( _, _rx = select(
ctx.done().recv, # 0 ctx.done().recv, # 0
...@@ -909,9 +912,9 @@ class tFile: ...@@ -909,9 +912,9 @@ class tFile:
) )
if _ == 0: if _ == 0:
raise ctx.err() raise ctx.err()
b = _rx got = _rx
ev.append('read ' + b) ev.append('read ' + b(got))
ev = doCheckingPin(ctx, _, pinokByWLink, pinfunc) ev = doCheckingPin(ctx, _, pinokByWLink, pinfunc)
# XXX hack - wlinks are notified and emit events simultaneously - we # XXX hack - wlinks are notified and emit events simultaneously - we
...@@ -1212,7 +1215,7 @@ def doCheckingPin(ctx, f, pinokByWLink, pinfunc=None): # -> []event(str) ...@@ -1212,7 +1215,7 @@ def doCheckingPin(ctx, f, pinokByWLink, pinfunc=None): # -> []event(str)
def _expectPin(twlink, ctx, zf, expect): # -> []SrvReq def _expectPin(twlink, ctx, zf, expect): # -> []SrvReq
expected = set() # of expected pin messages expected = set() # of expected pin messages
for blk, at in expect.items(): for blk, at in expect.items():
hat = h(at) if at is not None else 'head' hat = h(at) if at is not None else b'head'
msg = b"pin %s #%d @%s" % (h(zf._p_oid), blk, hat) msg = b"pin %s #%d @%s" % (h(zf._p_oid), blk, hat)
assert msg not in expected assert msg not in expected
expected.add(msg) expected.add(msg)
...@@ -1806,7 +1809,7 @@ def test_wcfs_remmap_on_pin(): ...@@ -1806,7 +1809,7 @@ def test_wcfs_remmap_on_pin():
assert at == at1 assert at == at1
mm.map_into_ro(f._blk(blk), f1.f.fileno(), blk*f.blksize) mm.map_into_ro(f._blk(blk), f1.f.fileno(), blk*f.blksize)
f._assertBlk(2, 'hello', {wl: {2:at1}}, pinfunc=_) # NOTE not world f._assertBlk(2, b('hello'), {wl: {2:at1}}, pinfunc=_) # NOTE not world
# verify that pin message is not sent for the same blk@at twice. # verify that pin message is not sent for the same blk@at twice.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment