~bigbes/tarantool

tarantool-protobuf

ref: b1d9c7581bcc6b4f299155746dc04acc2330e78c tarantool-protobuf/test/dynamic_test.lua -rw-r--r-- 4.7 KiB
b1d9c758 — Eugene Blikh test: pin unknown-fields text-format conformance regressions 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
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
-- Runtime parsing: load hello.proto at runtime and assert encode/decode
-- parity with the build-time generated module across the interop corpus.
local t = require('luatest')
local fio = require('fio')
local pb = require('pb')

local REPO_ROOT = fio.abspath(fio.pathjoin(
    fio.dirname(debug.getinfo(1, 'S').source:sub(2)), '..'))
local PROTO_PATH = fio.pathjoin(REPO_ROOT, 'examples', 'proto', 'hello.proto')
local FIXTURES_DIR = fio.pathjoin(REPO_ROOT, 'test', '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

local g_unit = t.group('parser.unit')

g_unit.test_parser_handles_full_schema = function()
    local source = slurp(PROTO_PATH)
    local ast = pb.parser.parse(source)
    t.assert_equals(ast.syntax, 'proto3')
    t.assert_equals(ast.package, 'hello')
    t.assert(#ast.messages > 0)
    -- Look up Person AST in flat list.
    local person
    for _, m in ipairs(ast.messages) do
        if m.name == 'Person' then person = m; break end
    end
    t.assert(person, 'Person message found')
    -- Locate `optional string apartment = 4;` on Address
    local address
    for _, m in ipairs(ast.messages) do
        if m.name == 'Address' then address = m; break end
    end
    t.assert(address)
    local apt
    for _, f in ipairs(address.fields) do
        if f.name == 'apartment' then apt = f; break end
    end
    t.assert(apt and apt.optional, 'apartment is optional')
end

g_unit.test_parser_handles_oneof_and_map = function()
    local source = slurp(PROTO_PATH)
    local ast = pb.parser.parse(source)
    -- Find Result; should have a oneof.
    local result
    for _, m in ipairs(ast.messages) do
        if m.name == 'Result' then result = m; break end
    end
    t.assert(result and #result.oneofs > 0)
    t.assert_equals(result.oneofs[1].name, 'outcome')
    -- Find Person; should have map fields.
    local person
    for _, m in ipairs(ast.messages) do
        if m.name == 'Person' then person = m; break end
    end
    local map_field
    for _, f in ipairs(person.fields) do
        if f.kind == 'map' then map_field = f; break end
    end
    t.assert(map_field, 'at least one map field')
end

-- ---------------------------------------------------------------------------
-- Interop: dynamic module must produce identical bytes to the generated
-- module for every fixture in the corpus.
-- ---------------------------------------------------------------------------

local hello_dynamic = pb.parse(slurp(PROTO_PATH))
local hello_static  = require('full.hello.hello_pb')

local function fixtures()
    local entries = fio.listdir(FIXTURES_DIR)
    table.sort(entries)
    local out = {}
    for _, name in ipairs(entries) do
        if name:match('%.bin$') then
            local base = name:sub(1, -5)
            local bin = fio.pathjoin(FIXTURES_DIR, name)
            local txt = fio.pathjoin(FIXTURES_DIR, base .. '.txtpb')
            local type_full
            for line in io.lines(txt) do
                local m = line:match('^# type:%s*(%S+)')
                if m then type_full = m; break end
            end
            out[#out + 1] = {base = base, bin = bin, full = type_full}
        end
    end
    return out
end

local g = t.group('parser.interop')
for _, fx in ipairs(fixtures()) do
    g['test_' .. fx.base] = function()
        local short = fx.full:gsub('^hello%.', '')
        local enc = hello_dynamic[short .. '_encode']
        local dec = hello_dynamic[short .. '_decode']
        t.assert(enc and dec, 'dynamic module has ' .. short)

        local golden = slurp(fx.bin)
        local decoded = dec(golden)
        local reencoded = enc(decoded)

        t.assert_equals(hex(reencoded), hex(golden),
            'dynamic round-trip diverges from golden for ' .. fx.base)
    end
end

-- ---------------------------------------------------------------------------
-- Cross-module: dynamic-decoded message can be re-encoded via the
-- generated module byte-for-byte, and vice versa.
-- ---------------------------------------------------------------------------
local g_cross = t.group('parser.cross_module')
g_cross.test_cross_module_byte_equality = function()
    for _, fx in ipairs(fixtures()) do
        local short = fx.full:gsub('^hello%.', '')
        local golden = slurp(fx.bin)
        local from_dyn  = hello_dynamic[short .. '_decode'](golden)
        local from_stat = hello_static[short .. '_decode'](golden)
        t.assert_equals(
            hex(hello_static[short .. '_encode'](from_dyn)),
            hex(hello_dynamic[short .. '_encode'](from_stat)),
            'cross-module mismatch on ' .. fx.base)
    end
end