~bigbes/tarantool

tarantool-protobuf

ref: feecf8e0fd69221131d1ea4a4f10f687e80def11 tarantool-protobuf/test/interop_test.lua -rw-r--r-- 3.8 KiB
feecf8e0 — Eugene Blikh codegen: inline 1-byte tag fast path at decode call sites 3 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
-- Cross-implementation interop: assert our codec is byte-for-byte equivalent
-- to mainline protoc.
--
-- For each fixture <name>.bin (the output of `protoc --encode=Type` against
-- the matching .txtpb), this suite:
--   (a) decodes the golden bytes with our decoder
--   (b) re-encodes the resulting Lua table with our encoder
--   (c) asserts the round-tripped bytes match the golden byte-for-byte
--
-- Step (c) is the strong claim: we don't just round-trip ourselves, we
-- agree on the wire with the canonical implementation.
local t = require('luatest')
local fio = require('fio')

local FIXTURES_DIR = fio.abspath(fio.pathjoin(
    fio.dirname(debug.getinfo(1, 'S').source:sub(2)), 'interop', 'fixtures'))

local function slurp(path)
    local f = assert(io.open(path, 'rb'))
    local s = f:read('*a')
    f:close()
    return s
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

-- Read the `# type: <FullName>` annotation from the matching .txtpb file
-- to learn which Lua message type to decode/encode with.
local function read_type(txtpb_path)
    for line in io.lines(txtpb_path) do
        local m = line:match('^# type:%s*(%S+)')
        if m then return m end
    end
    error('no `# type:` annotation in ' .. txtpb_path, 0)
end

local function lua_type_funcs(hello, full_name)
    -- "hello.Person" -> ("Person_encode", "Person_decode")
    local short = full_name:gsub('^hello%.', '')
    return hello[short .. '_encode'], hello[short .. '_decode']
end

-- Discover all fixtures once.
local function list_fixtures()
    local entries = fio.listdir(FIXTURES_DIR)
    table.sort(entries)
    local fixtures = {}
    for _, name in ipairs(entries) do
        if name:match('%.bin$') then
            local base = name:sub(1, -5)
            local bin_path = fio.pathjoin(FIXTURES_DIR, name)
            local txt_path = fio.pathjoin(FIXTURES_DIR, base .. '.txtpb')
            fixtures[#fixtures + 1] = {
                name = base,
                bin_path = bin_path,
                txt_path = txt_path,
                full_name = read_type(txt_path),
            }
        end
    end
    return fixtures
end

local FIXTURES = list_fixtures()

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

    for _, fx in ipairs(FIXTURES) do
        g['test_' .. fx.name] = function()
            local golden = slurp(fx.bin_path)
            local encode_fn, decode_fn = lua_type_funcs(hello, fx.full_name)
            t.assert(encode_fn, 'no encoder for ' .. fx.full_name)
            t.assert(decode_fn, 'no decoder for ' .. fx.full_name)

            local decoded = decode_fn(golden)
            local reencoded = encode_fn(decoded)

            t.assert_equals(hex(reencoded), hex(golden),
                ('byte-for-byte mismatch with protoc on %s'):format(fx.name))
        end
    end
end

-- Lazy passthrough: decode_lazy then :encode() must produce bytes
-- byte-identical to the golden. This is the strongest claim for the
-- read-only zero-copy path — untouched views skip re-emission entirely.
for _, mode in ipairs({'full', 'runtime'}) do
    local hello = require(mode .. '.hello.hello_pb')
    local g = t.group('interop_lazy.' .. mode)

    for _, fx in ipairs(FIXTURES) do
        g['test_' .. fx.name] = function()
            local golden = slurp(fx.bin_path)
            local short = fx.full_name:gsub('^hello%.', '')
            local decode_lazy_fn = hello[short .. '_decode_lazy']
            t.assert(decode_lazy_fn, 'no decode_lazy for ' .. fx.full_name)

            local v = decode_lazy_fn(golden)
            t.assert_equals(hex(v:encode()), hex(golden),
                ('lazy passthrough mismatch on %s'):format(fx.name))
        end
    end
end