Commit 38a786a5 authored by Kirill Smelkov's avatar Kirill Smelkov

wcfs: tests: Fix thinko in how py vs go timestamps are asserted

When verifying WCFS we are checking that mtime of particular bigfile
corresponds to last transaction that modified its data. However there is
a peculiarity that py and go construct time from TID a bit differently
with the difference going up to 1µs(*). With that wcfs_test.py, when
asserting on timestamps, tries to verify mtime returned by wcfs to tid
of corresponding transaction with that 1µs of tolerance.

There is a bug, however, in the implementation of tolerance logic. In
that logic, when tid is converted to timestamp on py side it is rounded to
6th decimal digit. And it used to work on py2 only due to the fact that
on py2 current time is measured to that same 6th decimal digit - to the
microsecond precision. That fact is relevant here because ZODB
constructs transaction IDs from current time, and if the time precision
is only 1µs so will tid, when converted back to time, also have that 1µs
precision.

However py3, after https://github.com/python/cpython/commit/ad95c2d25c5f,
started to use clock_gettime which has 1ns precision instead of 1µs precision of
gittimeofday used previously. This results in the breakage of the above
logic as demonstrated by the following failure:

        @func
        def test_wcfs_basic():
            t = tDB(); zf = t.zfile
            defer(t.close)

            # >>> lookup non-BigFile -> must be rejected
            with raises(OSError) as exc:
                t.wc._stat("head/bigfile/%s" % h(t.nonzfile._p_oid))
            assert exc.value.errno == EINVAL

            # >>> file initially empty
            f = t.open(zf)
            f.assertCache([])

    >       f.assertData ([], mtime=t.at0)

    wcfs_test.py:1337:
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    t = <wcfs.wcfs_test.tFile object at 0x7f674f0d7490>, dataokv = [], mtime = @at0 (03fb8b696044aebb)

        def assertData(t, dataokv, mtime=None):
            st = os.fstat(t.f.fileno())
            assert st.st_blksize == t.blksize
            assert st.st_size == len(dataokv)*t.blksize
            if mtime is not None:
    >           assert st.st_mtime == tidtime(mtime)
    E           assert 1727274802.5628808 == 1727274802.562881
    E            +  where 1727274802.5628808 = os.stat_result(st_mode=33060, st_ino=10, st_dev=71, st_nlink=1, st_uid=1000, st_gid=1000, st_size=0, st_atime=0, st_mtime=1727274802, st_ctime=1727274802).st_mtime
    E            +  and   1727274802.562881 = tidtime(@at0 (03fb8b696044aebb))

Here t.at0 is 03fb8b696044aebb which corresponds to 1727274802.5628808
timestamp with 7 digits after point. However tidtime, due to round(·, 6)
returns another float with only 6 digits after point which results in the check failure.

-> Fix that by adjusting the assert to explicitly verify that the difference in
   between st_mtime returned by wcfs and tidtime of corresponding transaction ≤ 1µs.

(*) see neo@9112f21e for details
parent e1c96b1d
...@@ -952,7 +952,10 @@ class tFile: ...@@ -952,7 +952,10 @@ class tFile:
assert st.st_blksize == t.blksize assert st.st_blksize == t.blksize
assert st.st_size == len(dataokv)*t.blksize assert st.st_size == len(dataokv)*t.blksize
if mtime is not None: if mtime is not None:
assert st.st_mtime == tidtime(mtime) # st_mtime comes from wcfs via tidtime/go
# ZODB/py vs ZODB/go time resolution is not better than 1µs
# see e.g. https://lab.nexedi.com/kirr/neo/commit/9112f21e
assert abs(st.st_mtime - tidtime(mtime)) <= 1e-6
cachev = t.cached() cachev = t.cached()
for blk, dataok in enumerate(dataokv): for blk, dataok in enumerate(dataokv):
...@@ -2007,13 +2010,7 @@ def writefile(path, data): ...@@ -2007,13 +2010,7 @@ def writefile(path, data):
# tidtime converts tid to transaction commit time. # tidtime converts tid to transaction commit time.
def tidtime(tid): def tidtime(tid):
t = TimeStamp(tid).timeTime() return TimeStamp(tid).timeTime()
# ZODB/py vs ZODB/go time resolution is not better than 1µs
# see e.g. https://lab.nexedi.com/kirr/neo/commit/9112f21e
#
# NOTE pytest.approx supports only ==, not e.g. <, so we use plain round.
return round(t, 6)
# tidfromtime converts time into corresponding transaction ID. # tidfromtime converts time into corresponding transaction ID.
def tidfromtime(t): def tidfromtime(t):
...@@ -2025,8 +2022,8 @@ def tidfromtime(t): ...@@ -2025,8 +2022,8 @@ def tidfromtime(t):
ts = TimeStamp(_.tm_year, _.tm_mon, _.tm_mday, _.tm_hour, _.tm_min, s) ts = TimeStamp(_.tm_year, _.tm_mon, _.tm_mday, _.tm_hour, _.tm_min, s)
return ts.raw() return ts.raw()
# verify that tidtime is precise enough to show difference in between transactions. # verify that tidtime is precise to show difference in between transactions.
# verify that tidtime -> tidfromtime is identity within rounding tolerance. # verify that tidtime -> tidfromtime is identity.
@func @func
def test_tidtime(): def test_tidtime():
t = tDB() t = tDB()
...@@ -2044,7 +2041,7 @@ def test_tidtime(): ...@@ -2044,7 +2041,7 @@ def test_tidtime():
tat = tidtime(at) tat = tidtime(at)
at_ = tidfromtime(tat) at_ = tidfromtime(tat)
tat_ = tidtime(at_) tat_ = tidtime(at_)
assert abs(tat_ - tat) <= 2E-6 assert tat_ == tat
# tAt is bytes whose repr returns human readable string considering it as `at` under tDB. # tAt is bytes whose repr returns human readable string considering it as `at` under tDB.
......
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