-- 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