~bigbes/tarantool

tarantool-protobuf

ref: 8b5bfc0e39a57a063cf6a5a8a80b9b7128ecd4c9 tarantool-protobuf/test/int64_as_number_test.lua -rw-r--r-- 6.0 KiB
8b5bfc0e — Eugene Blikh codegen: int64_as_number opt-in flag for 64-bit decode as Lua number (5y9) 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
-- 5y9: int64_as_number codegen option. Verifies that the opt-in `_n`
-- decoders return Lua number when the decoded value fits in [-2^53, 2^53]
-- and fall back to cdata otherwise. Encodes via the default (cdata-returning)
-- module and decodes via the `_n` module; assertions cover both type and
-- value across every 64-bit kind in c_int64.Wide.
--
-- examples/expected/full_n/c_int64/c_int64_pb.lua is regenerated by
-- `just gen-int64-as-number`, which runs the plugin with
-- `int64_as_number=true` for this single fixture so the default tree
-- stays cdata-typed and existing tests are unaffected. (5y9)
local t   = require('luatest')
local ffi = require('ffi')

local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local wide_default = require('full.c_int64.c_int64_pb')
local wide_n       = require('full_n.c_int64.c_int64_pb')

local g = t.group('int64_as_number')

-- The opt-in `_n` decoder lives in the pure-Lua codegen path. With
-- PB_ENABLE_C=1 the generated _decode prologue short-circuits to the
-- C runtime, which makes its own number-vs-cdata choice independent
-- of this codegen flag (luaL_pushint64 returns Lua number for values
-- that fit in double and cdata otherwise — the Tarantool convention).
-- Skip the type-stability assertions in that mode; the value-equality
-- behavior is still covered by the existing c_runtime_int64_test suite.
g.before_each(function()
    if os.getenv('PB_ENABLE_C') == '1' then
        t.skip('int64_as_number flag is a no-op under PB_ENABLE_C=1 — ' ..
               'the C runtime decides number-vs-cdata on its own')
    end
end)

-- Confirm the two fixtures share a wire format — the codegen flag only
-- changes the Lua return type, never the bytes that come off the wire.
g.test_encode_bytes_identical = function()
    local p = {
        a_int64    = INT64(-1234567),
        a_uint64   = UINT64(8765432),
        a_sint64   = INT64(-987654),
        a_fixed64  = UINT64(42),
        a_sfixed64 = INT64(-42),
    }
    t.assert_equals(wide_n.Wide_encode(p), wide_default.Wide_encode(p))
end

-- Values inside the 2^53 window should come back as plain Lua numbers
-- under the _n decoder, but stay cdata under the default decoder.
g.test_small_values_return_number = function()
    local p = {
        a_int64    = INT64(-123),
        a_uint64   = UINT64(456),
        a_sint64   = INT64(-789),
        a_fixed64  = UINT64(1011),
        a_sfixed64 = INT64(-1213),
    }
    local bytes = wide_default.Wide_encode(p)

    local d_n = wide_n.Wide_decode(bytes)
    t.assert_equals(type(d_n.a_int64),    'number')
    t.assert_equals(type(d_n.a_uint64),   'number')
    t.assert_equals(type(d_n.a_sint64),   'number')
    t.assert_equals(type(d_n.a_fixed64),  'number')
    t.assert_equals(type(d_n.a_sfixed64), 'number')

    t.assert_equals(d_n.a_int64,    -123)
    t.assert_equals(d_n.a_uint64,    456)
    t.assert_equals(d_n.a_sint64,   -789)
    t.assert_equals(d_n.a_fixed64,  1011)
    t.assert_equals(d_n.a_sfixed64, -1213)

    -- Default decoder stays cdata for everything 64-bit.
    local d = wide_default.Wide_decode(bytes)
    t.assert_equals(type(d.a_int64),    'cdata')
    t.assert_equals(type(d.a_uint64),   'cdata')
    t.assert_equals(type(d.a_sint64),   'cdata')
    t.assert_equals(type(d.a_fixed64),  'cdata')
    t.assert_equals(type(d.a_sfixed64), 'cdata')
end

-- Exactly 2^53 / -2^53 sit on the inclusive boundary (both are powers of
-- two and round-trip a Lua double exactly). Anything one notch past the
-- boundary on the magnitude side falls back to cdata.
g.test_boundary_2p53_returns_number = function()
    local p = {
        a_int64    = INT64( 0x20000000000000LL),  --  2^53
        a_sint64   = INT64(-0x20000000000000LL),  -- -2^53
        a_uint64   = UINT64(0x20000000000000ULL), --  2^53
        a_fixed64  = UINT64(0x20000000000000ULL), --  2^53
        a_sfixed64 = INT64(-0x20000000000000LL),  -- -2^53
    }
    local d_n = wide_n.Wide_decode(wide_default.Wide_encode(p))
    t.assert_equals(type(d_n.a_int64),    'number')
    t.assert_equals(type(d_n.a_sint64),   'number')
    t.assert_equals(type(d_n.a_uint64),   'number')
    t.assert_equals(type(d_n.a_fixed64),  'number')
    t.assert_equals(type(d_n.a_sfixed64), 'number')
    -- 2^53 is exact as a double; the value comparison passes.
    t.assert_equals(d_n.a_int64,    2 ^ 53)
    t.assert_equals(d_n.a_uint64,   2 ^ 53)
    t.assert_equals(d_n.a_fixed64,  2 ^ 53)
    t.assert_equals(d_n.a_sint64,  -(2 ^ 53))
    t.assert_equals(d_n.a_sfixed64, -(2 ^ 53))
end

g.test_past_2p53_returns_cdata = function()
    local p = {
        a_int64    = INT64( 0x40000000000000LL),  -- 2^54
        a_uint64   = UINT64(0x40000000000000ULL),
        a_sint64   = INT64(-0x40000000000000LL),
        a_fixed64  = UINT64(0x40000000000000ULL),
        a_sfixed64 = INT64(-0x40000000000000LL),
    }
    local d_n = wide_n.Wide_decode(wide_default.Wide_encode(p))
    t.assert_equals(type(d_n.a_int64),    'cdata')
    t.assert_equals(type(d_n.a_uint64),   'cdata')
    t.assert_equals(type(d_n.a_sint64),   'cdata')
    t.assert_equals(type(d_n.a_fixed64),  'cdata')
    t.assert_equals(type(d_n.a_sfixed64), 'cdata')
    -- Value still round-trips correctly (just as cdata, not number).
    t.assert_equals(d_n.a_int64,    INT64( 0x40000000000000LL))
    t.assert_equals(d_n.a_uint64,   UINT64(0x40000000000000ULL))
    t.assert_equals(d_n.a_sint64,   INT64(-0x40000000000000LL))
    t.assert_equals(d_n.a_fixed64,  UINT64(0x40000000000000ULL))
    t.assert_equals(d_n.a_sfixed64, INT64(-0x40000000000000LL))
end

-- Lua-number inputs round-trip clean too: encode accepts numbers (via the
-- existing encode wrappers), decode returns numbers under _n.
g.test_lua_number_inputs_round_trip = function()
    local p = {
        a_int64 = 100, a_uint64 = 200, a_sint64 = -300,
        a_fixed64 = 400, a_sfixed64 = -500,
    }
    local d_n = wide_n.Wide_decode(wide_default.Wide_encode(p))
    t.assert_equals(d_n.a_int64,    100)
    t.assert_equals(d_n.a_uint64,   200)
    t.assert_equals(d_n.a_sint64,   -300)
    t.assert_equals(d_n.a_fixed64,  400)
    t.assert_equals(d_n.a_sfixed64, -500)
end