~bigbes/tarantool

tarantool-protobuf

ref: c57b013b1af5f1520e8239b5bd610768f97ac72d tarantool-protobuf/test/c_runtime_unknown_test.lua -rw-r--r-- 7.6 KiB
c57b013b — Eugene Blikh beads: close ra6 3k 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
189
190
191
192
193
194
195
196
197
-- Test for bd-wyp / ra6 3j: C-side unknown-fields capture + re-emission.
--
-- A C-runtime decoder that meets fields not in the plan must capture
-- their raw bytes into result._unknown_fields, and a subsequent C
-- encode must re-emit them verbatim — same contract as
-- test/unknown_test.lua against the pure-Lua paths.
--
-- Gated on PB_ENABLE_C=1 + a loadable c_runtime module, same as the
-- sibling c_runtime_*_test.lua files.

local t = require('luatest')

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

local function hex(s)
    local out = {}
    for i = 1, #s do out[i] = string.format('%02x', s:byte(i)) end
    return table.concat(out)
end

-- Wire-tag for (field_id, wire_type). Multi-byte varint emission for
-- field ids > 15 — Address has fields 1..4 declared, so 50/51/52/53 are
-- guaranteed unknown.
local function tag_bytes(id, wt)
    local v = id * 8 + wt
    local out = {}
    while v >= 0x80 do
        out[#out + 1] = string.char(v % 0x80 + 0x80)
        v = math.floor(v / 0x80)
    end
    out[#out + 1] = string.char(v)
    return table.concat(out)
end

-- One byte-slice per wire type. Field ids picked outside Address (1..4).
local UNK_VARINT  = tag_bytes(50, 0) .. '\x2a'                      -- value 42
local UNK_I32     = tag_bytes(51, 5) .. '\x01\x00\x00\x00'          -- value 1
local UNK_I64     = tag_bytes(52, 1) .. '\x02\x00\x00\x00\x00\x00\x00\x00'
local UNK_LEN     = tag_bytes(53, 2) .. '\x03foo'                   -- 3-byte string

local ALL_UNK     = UNK_VARINT .. UNK_I32 .. UNK_I64 .. UNK_LEN

local full_hello

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

    g.before_all(function()
        skip_if_no_c()
        hello = require(mode .. '.hello.hello_pb')
        full_hello = require('full.hello.hello_pb')
        plan = c_runtime.compile_plan(hello.Address_descriptor)
    end)

    g.before_each(skip_if_no_c)

    function g.test_no_unknown_means_field_absent()
        local dec = c_runtime.decode(plan,
            c_runtime.encode(plan, {street = 'X'}))
        t.assert_equals(dec.street, 'X')
        t.assert_equals(dec._unknown_fields, nil,
            '_unknown_fields must be absent when input had only known fields')
    end

    function g.test_varint_unknown_round_trips()
        local known = c_runtime.encode(plan, {street = 'X', zip = 7})
        local mixed = known .. UNK_VARINT
        local dec = c_runtime.decode(plan, mixed)
        t.assert_equals(dec.street, 'X')
        t.assert_equals(dec.zip, 7)
        t.assert_equals(hex(dec._unknown_fields), hex(UNK_VARINT))
    end

    function g.test_all_wire_types_captured_in_order()
        local known = c_runtime.encode(plan, {street = 'X'})
        local mixed = known .. ALL_UNK
        local dec = c_runtime.decode(plan, mixed)
        t.assert_equals(dec.street, 'X')
        t.assert_equals(hex(dec._unknown_fields), hex(ALL_UNK),
            'all four wire types must be captured verbatim in source order')
    end

    function g.test_unknowns_interleaved_with_knowns()
        -- Bytes order: unknown, known, unknown — capture must preserve
        -- the two unknown chunks in encounter order, knowns stay parsed.
        local known1 = c_runtime.encode(plan, {street = 'A'})
        local known2 = c_runtime.encode(plan, {zip = 99})
        local mixed = UNK_VARINT .. known1 .. UNK_I64 .. known2
        local dec = c_runtime.decode(plan, mixed)
        t.assert_equals(dec.street, 'A')
        t.assert_equals(dec.zip, 99)
        t.assert_equals(hex(dec._unknown_fields), hex(UNK_VARINT .. UNK_I64))
    end

    function g.test_re_encode_preserves_unknown_bytes()
        local mixed = c_runtime.encode(plan, {street = 'X'}) .. ALL_UNK
        local dec = c_runtime.decode(plan, mixed)
        local re_enc = c_runtime.encode(plan, dec)
        local expected = c_runtime.encode(plan, {street = 'X'}) .. ALL_UNK
        t.assert_equals(hex(re_enc), hex(expected),
            'unknown bytes must be re-emitted verbatim at the tail')
    end

    function g.test_re_encode_decode_idempotent()
        local mixed = c_runtime.encode(plan, {street = 'X', zip = 1}) .. UNK_LEN
        local dec1 = c_runtime.decode(plan, mixed)
        local dec2 = c_runtime.decode(plan,
            c_runtime.encode(plan, dec1))
        t.assert_equals(dec2.street, 'X')
        t.assert_equals(dec2.zip, 1)
        t.assert_equals(hex(dec2._unknown_fields), hex(UNK_LEN))
    end

    function g.test_empty_unknown_string_treated_as_absent()
        -- User explicitly sets _unknown_fields = '' on encode; must be
        -- a no-op (mirrors codec.lua's nil/'' short-circuit).
        local enc = c_runtime.encode(plan,
            {street = 'X', _unknown_fields = ''})
        t.assert_equals(hex(enc),
            hex(c_runtime.encode(plan, {street = 'X'})))
    end

    function g.test_capture_matches_pure_lua_decode()
        -- Acceptance per bd-wyp: shape-for-shape parity with the pure-Lua
        -- decode for the same mixed input.
        local mixed = full_hello.Address_encode({street = 'X'}) .. ALL_UNK
        local c_dec   = c_runtime.decode(plan, mixed)
        local lua_dec = full_hello.Address_decode(mixed)
        t.assert_equals(hex(c_dec._unknown_fields),
                        hex(lua_dec._unknown_fields))
    end

    function g.test_reencode_matches_pure_lua_with_seeded_unknown_bytes()
        -- Table with _unknown_fields set — encode under C must agree
        -- byte-for-byte with the pure-Lua encoder.
        local tbl = {street = 'X', zip = 9, _unknown_fields = ALL_UNK}
        t.assert_equals(
            hex(c_runtime.encode(plan, tbl)),
            hex(full_hello.Address_encode(tbl)))
    end
end

-- ---------------------------------------------------------------------------
-- Nested-message coverage: each recursive decode_body gets its own
-- _unknown_fields buffer — parent and child must not bleed into each
-- other.
-- ---------------------------------------------------------------------------
local g_nested = t.group('c_runtime_unknown.nested')
local full_hello_n

g_nested.before_all(function()
    skip_if_no_c()
    full_hello_n = require('full.hello.hello_pb')
end)

g_nested.before_each(skip_if_no_c)

g_nested.test_inner_unknown_isolated_from_outer = function()
    -- Build a Person whose nested address payload includes an unknown
    -- trailing tag. The outer Person decode must NOT inherit the inner
    -- _unknown_fields. Acceptance for the per-frame buffer isolation.
    local person_plan = c_runtime.compile_plan(
        full_hello_n.Person_descriptor)
    local addr_plan   = c_runtime.compile_plan(
        full_hello_n.Address_descriptor)

    -- inner address body = encode({street = 'X'}) plus an unknown tag
    local addr_body = c_runtime.encode(addr_plan, {street = 'X'})
                      .. UNK_VARINT
    -- outer person body wraps that as a length-delimited field id=5
    -- (Person.address). Tag = (5<<3)|2 = 0x2A.
    local function uvarint(v)
        local out = {}
        while v >= 0x80 do
            out[#out + 1] = string.char(v % 0x80 + 0x80)
            v = math.floor(v / 0x80)
        end
        out[#out + 1] = string.char(v)
        return table.concat(out)
    end
    local person_body = '\x2a' .. uvarint(#addr_body) .. addr_body

    local dec = c_runtime.decode(person_plan, person_body)
    t.assert_equals(dec._unknown_fields, nil,
        'outer Person must not carry the inner Address unknown bytes')
    t.assert_equals(dec.address.street, 'X')
    t.assert_equals(hex(dec.address._unknown_fields), hex(UNK_VARINT))
end