-- pb.from_pb: build runtime modules from a binary FileDescriptorSet. -- -- We shell out to mainline `protoc --descriptor_set_out` at test start to -- produce a fresh descriptor set for examples/proto/hello.proto, then -- assert that the module pb.from_pb returns produces wire-compatible bytes -- when matched against the statically-generated module. 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_DIR = fio.pathjoin(REPO_ROOT, 'examples', 'proto') local OPTIONS_DIR = fio.pathjoin(REPO_ROOT, 'options') local FIXTURE_PATH = fio.pathjoin(fio.tempdir(), 'hello.descpb') local function slurp(path) local f = assert(io.open(path, 'rb')) local s = f:read('*a') f:close() return s end -- Materialize once for all tests in this file. local SET_BYTES do local cmd = string.format( 'protoc --descriptor_set_out=%q -I %q -I %q %q', FIXTURE_PATH, PROTO_DIR, OPTIONS_DIR, fio.pathjoin(PROTO_DIR, 'hello.proto')) local ok = os.execute(cmd) assert(ok == 0 or ok == true, 'protoc --descriptor_set_out failed: ' .. cmd) SET_BYTES = slurp(FIXTURE_PATH) end local g = t.group('fileset') -- ---- structure --------------------------------------------------------- g.test_set_top_level_shape = function() local set = pb.from_pb(SET_BYTES) t.assert_type(set.files, 'table') t.assert_type(set.order, 'table') t.assert_type(set.lookup, 'function') t.assert_equals(#set.order, 1) -- protoc encodes paths relative to the -I argument that matched. t.assert_str_contains(set.order[1], 'hello.proto') end g.test_module_has_expected_descriptors = function() local set = pb.from_pb(SET_BYTES) local m = set.files[set.order[1]] t.assert_type(m.Person_descriptor, 'table') t.assert_type(m.Address_descriptor, 'table') t.assert_type(m.Result_descriptor, 'table') t.assert_type(m.Event_descriptor, 'table') t.assert_type(m.Status_descriptor, 'table') t.assert_type(m.Person_encode, 'function') t.assert_type(m.Person_decode, 'function') end g.test_lookup_by_full_name = function() local set = pb.from_pb(SET_BYTES) local desc = set.lookup('hello.Person') t.assert_type(desc, 'table') t.assert_equals(desc.name, 'hello.Person') t.assert_equals(set.lookup('does.not.Exist'), nil) end -- ---- parity with the statically-generated module ---------------------- local function static() return require('full.hello.hello_pb') end local function dyn() local set = pb.from_pb(SET_BYTES) return set.files[set.order[1]] end g.test_parity_scalars = function() local input = {street = 'Pushkina 1', city = 'Moscow', zip = 123456} t.assert_equals(dyn().Address_encode(input), static().Address_encode(input)) end g.test_parity_repeated_packed = function() local input = {name = 'P', lucky_numbers = {1, 2, 3, 4, 5}} t.assert_equals(dyn().Person_encode(input), static().Person_encode(input)) end g.test_parity_optional_field_presence = function() -- Empty `apartment` is meaningful and must round-trip. local input = {street = 'Main', apartment = ''} t.assert_equals(dyn().Address_encode(input), static().Address_encode(input)) end g.test_parity_oneof = function() local input = {id = 7, text = 'hi'} t.assert_equals(dyn().Result_encode(input), static().Result_encode(input)) end g.test_parity_oneof_message = function() local input = {id = 3, details = {street = 'X', zip = 99}} t.assert_equals(dyn().Result_encode(input), static().Result_encode(input)) end g.test_parity_map_scalar = function() -- Single-key map; multi-key bytes depend on Lua hash order (see -- "Map fixtures are fragile" note in CLAUDE.md), so use one entry. local input = {name = 'P', ages_by_nickname = {alice = 30}} t.assert_equals(dyn().Person_encode(input), static().Person_encode(input)) end g.test_parity_map_message_value = function() local input = {name = 'P', addresses_by_label = {home = {street = 'Main', zip = 1}}} t.assert_equals(dyn().Person_encode(input), static().Person_encode(input)) end g.test_parity_self_reference = function() local input = { name = 'P', friends = {{name = 'A'}, {name = 'B', age = 30}}, } t.assert_equals(dyn().Person_encode(input), static().Person_encode(input)) end g.test_parity_enum_by_name = function() local s = static() local input = {name = 'P', status = s.Status.ERROR} t.assert_equals(dyn().Person_encode(input), s.Person_encode(input)) end -- ---- WKT references resolved through pb.wkt ---------------------------- g.test_parity_wkt_timestamp_table = function() local input = {title = 'x', created_at = {seconds = 1700000000, nanos = 5}} t.assert_equals(dyn().Event_encode(input), static().Event_encode(input)) end g.test_parity_wkt_wrappers = function() local input = {title = 'x', retry_count = 7, note = 'hello', is_admin = true} t.assert_equals(dyn().Event_encode(input), static().Event_encode(input)) end g.test_parity_wkt_fieldmask = function() local input = {title = 'x', update_mask = {'a', 'b', 'c'}} t.assert_equals(dyn().Event_encode(input), static().Event_encode(input)) end g.test_parity_wkt_empty = function() local input = {title = 'x', ack = {}} t.assert_equals(dyn().Event_encode(input), static().Event_encode(input)) end -- ---- round-trip via dynamic decode then static decode ------------------ g.test_round_trip_through_dynamic = function() -- Encode with the static module, decode with the dynamic one, re-encode -- with the dynamic one; check the bytes match. local s = static() local input = { name = 'P', age = 33, emails = {'a@x', 'b@y'}, status = s.Status.OK, address = {street = 'Main', city = 'X', zip = 1}, lucky_numbers = {1, 2, 3}, } local bytes = s.Person_encode(input) local decoded = dyn().Person_decode(bytes) t.assert_equals(dyn().Person_encode(decoded), bytes) end