~bigbes/tarantool

tarantool-protobuf

ref: 3235f34af486e3ef0043f8cb9d9230e29b919980 tarantool-protobuf/test/c_runtime_int64_test.lua -rw-r--r-- 8.0 KiB
3235f34a — Eugene Blikh c_runtime: proto2 — required, defaults, groups, extensions (ra6 3i) 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
-- Test for bd-awv / ra6 3l: 64-bit cdata fidelity.
--
-- int64/uint64/sint64/fixed64/sfixed64 must round-trip as LuaJIT
-- int64_t/uint64_t cdata through the C runtime — never narrowed to
-- a Lua double. Same convention as Tarantool's msgpackffi, net.box,
-- box.tuple, and the built-in protobuf. The C decode path pushes
-- cdata via luaL_pushint64 / luaL_pushuint64; the C encode path
-- accepts cdata via luaL_toint64 / luaL_touint64 alongside plain
-- Lua numbers.
--
-- Acceptance per bd-awv:
--   A value > 2^53 round-trips byte-equal to mode=full pure-Lua, and
--   the decoded value remains cdata. Covers all five 64-bit kinds in
--   both codegen modes. Only runs when PB_ENABLE_C=1 is set and the
--   C runtime module is loadable.

local t = require('luatest')
local ffi = require('ffi')

local pb = require('pb')
local c_runtime = pb.c_runtime

local function skip_if_no_c()
    if c_runtime == nil then
        t.skip('PB_ENABLE_C not set or pb.c_runtime not available')
    end
end

-- Values past Lua's 2^53 double-precision boundary, picked so the
-- top bits would silently drop if any code path narrowed to double.
-- int64_max - 1, uint64_max - 1, and high bit patterns for fixed.
local U64_BIG     = 0xDEADBEEFCAFEBABEULL
local U64_MAX_ISH = 0xFFFFFFFFFFFFFFFEULL  -- one off all-ones
local I64_NEG_BIG = -0x0123456789ABCDEFLL  -- past -2^53
local I64_POS_BIG =  0x0123456789ABCDEFLL  -- past  2^53

local full_wide

for _, mode in ipairs({'full', 'runtime'}) do
    local g = t.group('c_runtime_int64.' .. mode)
    local wide

    g.before_all(function()
        skip_if_no_c()
        wide = require(mode .. '.c_int64.c_int64_pb')
        full_wide = require('full.c_int64.c_int64_pb')
    end)

    g.before_each(skip_if_no_c)

    -- ---------- Acceptance per bd-awv ----------

    function g.test_acceptance_all_kinds_round_trip_past_2pow53()
        -- One message carries every 64-bit kind with a value that
        -- would lose precision if narrowed through a Lua double.
        -- Bytes must match mode=full, and every decoded field must
        -- remain cdata.
        local plan = c_runtime.compile_plan(wide.Wide_descriptor)
        local msg = {
            a_int64    = ffi.new('int64_t',  I64_POS_BIG),
            a_uint64   = U64_BIG,
            a_sint64   = ffi.new('int64_t',  I64_NEG_BIG),
            a_fixed64  = U64_MAX_ISH,
            a_sfixed64 = ffi.new('int64_t',  I64_NEG_BIG),
        }
        local c_bytes   = c_runtime.encode(plan, msg)
        local lua_bytes = full_wide.Wide_encode(msg)
        t.assert_equals(c_bytes, lua_bytes, 'encode bytes match mode=full')

        local c_decoded = c_runtime.decode(plan, c_bytes)
        local lua_decoded = full_wide.Wide_decode(c_bytes)
        t.assert_equals(c_decoded, lua_decoded, 'decoded table matches mode=full')

        -- Every value must surface as cdata, not double.
        for _, k in ipairs({'a_int64', 'a_uint64', 'a_sint64',
                            'a_fixed64', 'a_sfixed64'}) do
            t.assert_equals(type(c_decoded[k]), 'cdata',
                ('field %s decoded as %s, expected cdata')
                :format(k, type(c_decoded[k])))
        end

        -- Value comparison done via cdata equality so the top bits
        -- are checked, not just the low 53.
        t.assert_equals(c_decoded.a_int64,   msg.a_int64)
        t.assert_equals(c_decoded.a_uint64,  msg.a_uint64)
        t.assert_equals(c_decoded.a_sint64,  msg.a_sint64)
        t.assert_equals(c_decoded.a_fixed64, msg.a_fixed64)
        t.assert_equals(c_decoded.a_sfixed64, msg.a_sfixed64)
    end

    -- ---------- Per-kind: encode from cdata + decode preserves cdata --

    function g.test_int64_cdata_round_trip()
        local plan = c_runtime.compile_plan(wide.Wide_descriptor)
        local v = ffi.new('int64_t', I64_POS_BIG)
        local bytes = c_runtime.encode(plan, {a_int64 = v})
        t.assert_equals(bytes, full_wide.Wide_encode({a_int64 = v}))
        local d = c_runtime.decode(plan, bytes)
        t.assert_equals(type(d.a_int64), 'cdata')
        t.assert_equals(d.a_int64, v)
    end

    function g.test_int64_negative_cdata_round_trip()
        local plan = c_runtime.compile_plan(wide.Wide_descriptor)
        -- Negative int64 is sign-extended to 10-byte varint on the wire.
        local v = ffi.new('int64_t', I64_NEG_BIG)
        local bytes = c_runtime.encode(plan, {a_int64 = v})
        t.assert_equals(bytes, full_wide.Wide_encode({a_int64 = v}))
        local d = c_runtime.decode(plan, bytes)
        t.assert_equals(type(d.a_int64), 'cdata')
        t.assert_equals(d.a_int64, v)
    end

    function g.test_uint64_cdata_round_trip()
        local plan = c_runtime.compile_plan(wide.Wide_descriptor)
        local v = U64_BIG
        local bytes = c_runtime.encode(plan, {a_uint64 = v})
        t.assert_equals(bytes, full_wide.Wide_encode({a_uint64 = v}))
        local d = c_runtime.decode(plan, bytes)
        t.assert_equals(type(d.a_uint64), 'cdata')
        t.assert_equals(d.a_uint64, v)
    end

    function g.test_uint64_near_max_cdata_round_trip()
        local plan = c_runtime.compile_plan(wide.Wide_descriptor)
        local v = U64_MAX_ISH
        local bytes = c_runtime.encode(plan, {a_uint64 = v})
        t.assert_equals(bytes, full_wide.Wide_encode({a_uint64 = v}))
        local d = c_runtime.decode(plan, bytes)
        t.assert_equals(type(d.a_uint64), 'cdata')
        t.assert_equals(d.a_uint64, v)
    end

    function g.test_sint64_cdata_round_trip()
        local plan = c_runtime.compile_plan(wide.Wide_descriptor)
        local v = ffi.new('int64_t', I64_NEG_BIG)
        local bytes = c_runtime.encode(plan, {a_sint64 = v})
        t.assert_equals(bytes, full_wide.Wide_encode({a_sint64 = v}))
        local d = c_runtime.decode(plan, bytes)
        t.assert_equals(type(d.a_sint64), 'cdata')
        t.assert_equals(d.a_sint64, v)
    end

    function g.test_fixed64_cdata_round_trip()
        local plan = c_runtime.compile_plan(wide.Wide_descriptor)
        local v = U64_BIG
        local bytes = c_runtime.encode(plan, {a_fixed64 = v})
        t.assert_equals(bytes, full_wide.Wide_encode({a_fixed64 = v}))
        -- fixed64 is always 8 bytes + 1 tag byte; spot-check the wire size.
        t.assert_equals(#bytes, 9, 'fixed64 wire size = tag + 8B')
        local d = c_runtime.decode(plan, bytes)
        t.assert_equals(type(d.a_fixed64), 'cdata')
        t.assert_equals(d.a_fixed64, v)
    end

    function g.test_sfixed64_cdata_round_trip()
        local plan = c_runtime.compile_plan(wide.Wide_descriptor)
        local v = ffi.new('int64_t', I64_NEG_BIG)
        local bytes = c_runtime.encode(plan, {a_sfixed64 = v})
        t.assert_equals(bytes, full_wide.Wide_encode({a_sfixed64 = v}))
        local d = c_runtime.decode(plan, bytes)
        t.assert_equals(type(d.a_sfixed64), 'cdata')
        t.assert_equals(d.a_sfixed64, v)
    end

    -- ---------- Encode also accepts plain Lua numbers (in-range) ------

    function g.test_int64_from_lua_number_within_double_range()
        -- Plain Lua numbers below 2^53 must encode identically to
        -- the cdata path. This is the common case for IDs/counters
        -- that happen to fit a double exactly. Tarantool's
        -- luaL_pushint64 surfaces small values as Lua numbers (not
        -- cdata) by convention — same as msgpackffi, net.box, the
        -- built-in protobuf — so the decoded type tracks that, and
        -- equality compares numerically across number/cdata.
        local plan = c_runtime.compile_plan(wide.Wide_descriptor)
        local msg_num   = {a_int64 = 42}
        local msg_cdata = {a_int64 = ffi.new('int64_t', 42)}
        local b_num = c_runtime.encode(plan, msg_num)
        local b_cd  = c_runtime.encode(plan, msg_cdata)
        t.assert_equals(b_num, b_cd, 'Lua number encodes same as cdata')
        t.assert_equals(b_num, full_wide.Wide_encode(msg_num))
        local d = c_runtime.decode(plan, b_num)
        t.assert_equals(d.a_int64, 42)
        t.assert_equals(d, full_wide.Wide_decode(b_num),
            'C decode matches mode=full shape-for-shape')
    end
end