-- Smoke test for bd-mq7: descriptor → C plan compiler. -- -- Only runs when PB_ENABLE_C=1 is set in the environment AND the C -- runtime module is loadable. Otherwise the group is skipped, which -- keeps `just test` green on hosts without the C module built. -- -- Acceptance per bd-mq7: -- (1) pb.c_runtime.compile_plan(desc) returns a userdata -- (2) plan->n_fields and plan->fields[i].tag are readable -- (3) Plans for hello.Person and hello.Address build without error 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 -- Run each test against both codegen modes — c_plan is attached to the -- descriptor regardless of mode, so both pick up the same compile path. for _, mode in ipairs({'full', 'runtime'}) do local g = t.group('c_runtime_plan.' .. mode) local hello g.before_all(function() skip_if_no_c() hello = require(mode .. '.hello.hello_pb') end) g.before_each(skip_if_no_c) function g.test_module_surface() t.assert_equals(type(c_runtime.compile_plan), 'function') t.assert_equals(type(c_runtime._abi_version), 'string') t.assert(c_runtime.KIND ~= nil, 'KIND table exposed') t.assert(c_runtime.WIRE ~= nil, 'WIRE table exposed') t.assert_equals(c_runtime.WIRE.LEN, 2) t.assert_equals(c_runtime.WIRE.VARINT, 0) end function g.test_compile_address_returns_userdata() local plan = c_runtime.compile_plan(hello.Address_descriptor) t.assert_equals(type(plan), 'userdata') t.assert_equals(c_runtime.plan_name(plan), 'hello.Address') t.assert_equals(c_runtime.plan_n_fields(plan), 4) end function g.test_address_field_shapes() local plan = c_runtime.compile_plan(hello.Address_descriptor) -- {name="street", id=1, kind=scalar/string} local f1 = c_runtime.plan_field_info(plan, 1) t.assert_equals(f1.name, 'street') t.assert_equals(f1.field_number, 1) t.assert_equals(f1.wire_type, c_runtime.WIRE.LEN) t.assert_equals(f1.kind, c_runtime.KIND.STRING) t.assert_equals(f1.tag_bytes:byte(1, 1), 0x0A) -- (1<<3)|2 -- {name="apartment", id=4, kind=scalar/string, optional=true} local f4 = c_runtime.plan_field_info(plan, 4) t.assert_equals(f4.name, 'apartment') t.assert_equals(f4.optional, true) t.assert_equals(f4.field_number, 4) end function g.test_compile_person_returns_userdata() local plan = c_runtime.compile_plan(hello.Person_descriptor) t.assert_equals(type(plan), 'userdata') t.assert_equals(c_runtime.plan_name(plan), 'hello.Person') -- Person has 14 fields per examples/proto/hello.proto t.assert_equals(c_runtime.plan_n_fields(plan), 14) end function g.test_person_scalar_fields() local plan = c_runtime.compile_plan(hello.Person_descriptor) -- name = string @1 local f = c_runtime.plan_field_info(plan, 1) t.assert_equals(f.name, 'name') t.assert_equals(f.kind, c_runtime.KIND.STRING) t.assert_equals(f.wire_type, c_runtime.WIRE.LEN) -- age = int32 @2 f = c_runtime.plan_field_info(plan, 2) t.assert_equals(f.name, 'age') t.assert_equals(f.kind, c_runtime.KIND.INT32) t.assert_equals(f.wire_type, c_runtime.WIRE.VARINT) -- user_id = fixed64 @9 f = c_runtime.plan_field_info(plan, 9) t.assert_equals(f.name, 'user_id') t.assert_equals(f.kind, c_runtime.KIND.FIXED64) t.assert_equals(f.wire_type, c_runtime.WIRE.I64) -- balance = sint32 @10 f = c_runtime.plan_field_info(plan, 10) t.assert_equals(f.kind, c_runtime.KIND.SINT32) -- weight_kg = double @11 f = c_runtime.plan_field_info(plan, 11) t.assert_equals(f.kind, c_runtime.KIND.DOUBLE) t.assert_equals(f.wire_type, c_runtime.WIRE.I64) end function g.test_person_repeated_packed() local plan = c_runtime.compile_plan(hello.Person_descriptor) -- emails = repeated string @3 (not packed) local f = c_runtime.plan_field_info(plan, 3) t.assert_equals(f.name, 'emails') t.assert_equals(f.repeated, true) t.assert_equals(f.packed, false) t.assert_equals(f.wire_type, c_runtime.WIRE.LEN) -- lucky_numbers = repeated int32, packed @7 f = c_runtime.plan_field_info(plan, 7) t.assert_equals(f.name, 'lucky_numbers') t.assert_equals(f.repeated, true) t.assert_equals(f.packed, true) -- packed flips wire type to LEN regardless of element type t.assert_equals(f.wire_type, c_runtime.WIRE.LEN) t.assert_equals(f.kind, c_runtime.KIND.INT32) end function g.test_person_enum_field() local plan = c_runtime.compile_plan(hello.Person_descriptor) -- status = enum @4 local f = c_runtime.plan_field_info(plan, 4) t.assert_equals(f.name, 'status') t.assert_equals(f.kind, c_runtime.KIND.ENUM) t.assert_equals(f.wire_type, c_runtime.WIRE.VARINT) end function g.test_person_message_field_resolves_sub_plan() local plan = c_runtime.compile_plan(hello.Person_descriptor) -- address = message Address @5 local f = c_runtime.plan_field_info(plan, 5) t.assert_equals(f.name, 'address') t.assert_equals(f.kind, c_runtime.KIND.MESSAGE) t.assert(f.sub_plan_idx > 0, 'sub_plan_idx populated') local sub = c_runtime.plan_sub_plan(plan, f.sub_plan_idx) t.assert_equals(type(sub), 'userdata') t.assert_equals(c_runtime.plan_name(sub), 'hello.Address') end function g.test_self_reference_cycle() local plan = c_runtime.compile_plan(hello.Person_descriptor) -- friends = repeated Person @6 (self-reference) local f = c_runtime.plan_field_info(plan, 6) t.assert_equals(f.name, 'friends') t.assert_equals(f.kind, c_runtime.KIND.MESSAGE) t.assert_equals(f.repeated, true) t.assert(f.sub_plan_idx > 0) local sub = c_runtime.plan_sub_plan(plan, f.sub_plan_idx) t.assert_equals(c_runtime.plan_name(sub), 'hello.Person') -- Cycle resolves to the same userdata, not a fresh one t.assert(sub == plan, 'self-reference returns the same plan userdata (cycle broken)') end function g.test_person_map_field() local plan = c_runtime.compile_plan(hello.Person_descriptor) -- ages_by_nickname: map @13 local f = c_runtime.plan_field_info(plan, 12) -- 12th field t.assert_equals(f.name, 'ages_by_nickname') t.assert_equals(f.kind, c_runtime.KIND.MAP) t.assert_equals(f.map_key_kind, c_runtime.KIND.STRING) t.assert_equals(f.map_value_kind, c_runtime.KIND.INT32) -- addresses_by_label: map @15 f = c_runtime.plan_field_info(plan, 14) t.assert_equals(f.name, 'addresses_by_label') t.assert_equals(f.map_value_kind, c_runtime.KIND.MESSAGE) t.assert(f.map_value_sub_plan_idx > 0) local sub = c_runtime.plan_sub_plan(plan, f.map_value_sub_plan_idx) t.assert_equals(c_runtime.plan_name(sub), 'hello.Address') end function g.test_idempotent_compile() local p1 = c_runtime.compile_plan(hello.Person_descriptor) local p2 = c_runtime.compile_plan(hello.Person_descriptor) t.assert(p1 == p2, 'second compile returns cached plan') end function g.test_result_oneof() local plan = c_runtime.compile_plan(hello.Result_descriptor) t.assert_equals(c_runtime.plan_n_fields(plan), 4) t.assert_equals(c_runtime.plan_n_oneofs(plan), 1) local o = c_runtime.plan_oneof_info(plan, 1) t.assert_equals(o.name, 'outcome') t.assert_equals(#o.member_indices, 3) -- Members are 0-based indices into plan->fields[]; text/code/details -- are fields 2, 3, 4 in Result (id-ordered) → indices 1, 2, 3. local idxs = {} for _, i in ipairs(o.member_indices) do idxs[i] = true end t.assert(idxs[1] and idxs[2] and idxs[3], 'oneof members map to text/code/details indices') -- Each member field gets oneof_idx = 0 (the only oneof in Result). for _, fi in ipairs({2, 3, 4}) do local f = c_runtime.plan_field_info(plan, fi) t.assert_equals(f.oneof_idx, 0, ('field %d oneof_idx'):format(fi)) end -- id (field 1) is NOT in a oneof. local f1 = c_runtime.plan_field_info(plan, 1) t.assert_equals(f1.oneof_idx, -1) end function g.test_wkt_override_detection() local wkt = pb.wkt local plan = c_runtime.compile_plan(wkt.Timestamp_descriptor) t.assert_equals(c_runtime.plan_has_override(plan), true) -- has_override means the plan does not walk fields; field count is 0. t.assert_equals(c_runtime.plan_n_fields(plan), 0) end end