-- Protobuf text-format printer tests. local t = require('luatest') local ffi = require('ffi') local pb = require('pb') local datetime = require('datetime') -- Both codegen modes share the same descriptors; the printer walks -- descriptors only, so the output is mode-independent. Exercise both -- to confirm parity. local MODES = {'full', 'runtime'} for _, mode in ipairs(MODES) do local g = t.group('text.' .. mode) local hello = require(mode .. '.hello.hello_pb') -- ---- scalars ------------------------------------------------------- g.test_empty_message = function() t.assert_equals(pb.text.encode(hello.Address_descriptor, {}), '') end g.test_basic_scalars = function() local s = pb.text.encode(hello.Address_descriptor, {street = 'Pushkina 1', city = 'Moscow', zip = 123456}) t.assert_equals(s, 'street: "Pushkina 1"\ncity: "Moscow"\nzip: 123456\n') end g.test_proto3_default_elision = function() -- Defaults on non-optional fields are elided like protoc --decode. local s = pb.text.encode(hello.Address_descriptor, {street = '', city = '', zip = 0}) t.assert_equals(s, '') end g.test_optional_field_emits_default = function() -- `apartment` is proto3 explicit-optional; explicit empty must -- round-trip even though it equals the scalar default. local s = pb.text.encode(hello.Address_descriptor, {street = 'Main', zip = 1, apartment = ''}) t.assert_equals(s, 'street: "Main"\nzip: 1\napartment: ""\n') end g.test_int64_lossless = function() local s = pb.text.encode(hello.Person_descriptor, {user_id = ffi.cast('uint64_t', 18369917520866213889ULL)}) t.assert_str_contains(s, 'user_id: 18369917520866213889') end g.test_bytes_octal_escape = function() local s = pb.text.encode(hello.Person_descriptor, {avatar = '\x00\x01\x02\xff'}) t.assert_str_contains(s, 'avatar: "\\000\\001\\002\\377"') end g.test_string_escapes = function() local s = pb.text.encode(hello.Person_descriptor, {name = 'a"b\\c\nd\te'}) t.assert_str_contains(s, 'name: "a\\"b\\\\c\\nd\\te"') end g.test_enum_as_name = function() local s = pb.text.encode(hello.Person_descriptor, {status = hello.Status.ERROR}) t.assert_str_contains(s, 'status: ERROR') end g.test_enum_unknown_as_number = function() local s = pb.text.encode(hello.Person_descriptor, {status = 42}) t.assert_str_contains(s, 'status: 42') end g.test_float_formats = function() local s = pb.text.encode(hello.Person_descriptor, {weight_kg = 72.5}) t.assert_str_contains(s, 'weight_kg: 72.5') end g.test_float_specials = function() local s = pb.text.encode(hello.Person_descriptor, {weight_kg = 0/0}) t.assert_str_contains(s, 'weight_kg: nan') s = pb.text.encode(hello.Person_descriptor, {weight_kg = math.huge}) t.assert_str_contains(s, 'weight_kg: inf') s = pb.text.encode(hello.Person_descriptor, {weight_kg = -math.huge}) t.assert_str_contains(s, 'weight_kg: -inf') end -- ---- repeated ------------------------------------------------------ g.test_repeated_scalar = function() local s = pb.text.encode(hello.Person_descriptor, {lucky_numbers = {1, 2, 3}}) t.assert_equals(s, 'lucky_numbers: 1\nlucky_numbers: 2\nlucky_numbers: 3\n') end g.test_repeated_string = function() local s = pb.text.encode(hello.Person_descriptor, {emails = {'a@x', 'b@x'}}) t.assert_equals(s, 'emails: "a@x"\nemails: "b@x"\n') end -- ---- nested message + oneof --------------------------------------- g.test_nested_message = function() local s = pb.text.encode(hello.Person_descriptor, { name = 'P', address = {street = 'Main', city = 'X', zip = 1}, }) t.assert_equals(s, 'name: "P"\naddress {\n street: "Main"\n city: "X"\n zip: 1\n}\n') end g.test_oneof_scalar = function() local s = pb.text.encode(hello.Result_descriptor, {id = 7, text = 'hi'}) t.assert_equals(s, 'id: 7\ntext: "hi"\n') end g.test_oneof_message = function() local s = pb.text.encode(hello.Result_descriptor, {id = 3, details = {street = 'X', zip = 99}}) t.assert_equals(s, 'id: 3\ndetails {\n street: "X"\n zip: 99\n}\n') end -- ---- map ----------------------------------------------------------- g.test_map_single_entry = function() local s = pb.text.encode(hello.Person_descriptor, {ages_by_nickname = {alice = 30}}) t.assert_equals(s, 'ages_by_nickname {\n key: "alice"\n value: 30\n}\n') end g.test_map_message_value = function() local s = pb.text.encode(hello.Person_descriptor, {addresses_by_label = {home = {street = 'Main', zip = 1}}}) t.assert_str_contains(s, 'addresses_by_label {') t.assert_str_contains(s, 'key: "home"') t.assert_str_contains(s, 'value {') t.assert_str_contains(s, 'street: "Main"') end -- ---- single-line -------------------------------------------------- g.test_single_line = function() local s = pb.text.encode(hello.Address_descriptor, {street = 'X', zip = 1}, {single_line = true}) t.assert_equals(s, 'street: "X" zip: 1') end g.test_single_line_nested = function() local s = pb.text.encode(hello.Person_descriptor, {name = 'P', address = {street = 'X', zip = 1}}, {single_line = true}) t.assert_equals(s, 'name: "P" address { street: "X" zip: 1 }') end -- ---- WKT ---------------------------------------------------------- g.test_wkt_empty = function() local s = pb.text.encode(hello.Event_descriptor, {title = 'x', ack = {}}) t.assert_equals(s, 'title: "x"\nack {}\n') end g.test_wkt_timestamp_from_table = function() local s = pb.text.encode(hello.Event_descriptor, {created_at = {seconds = 1700000000, nanos = 123}}) t.assert_str_contains(s, 'created_at {') t.assert_str_contains(s, 'seconds: 1700000000') t.assert_str_contains(s, 'nanos: 123') end g.test_wkt_timestamp_from_datetime = function() local dt = datetime.new({timestamp = 1700000000}) local s = pb.text.encode(hello.Event_descriptor, {created_at = dt}) t.assert_str_contains(s, 'seconds: 1700000000') end g.test_wkt_duration = function() local s = pb.text.encode(hello.Event_descriptor, {duration = {seconds = 5}}) t.assert_str_contains(s, 'duration {') t.assert_str_contains(s, 'seconds: 5') end g.test_wkt_wrapper_int32 = function() local s = pb.text.encode(hello.Event_descriptor, {retry_count = 7}) t.assert_str_contains(s, 'retry_count {') t.assert_str_contains(s, 'value: 7') end g.test_wkt_wrapper_string = function() local s = pb.text.encode(hello.Event_descriptor, {note = 'hello'}) t.assert_str_contains(s, 'note {') t.assert_str_contains(s, 'value: "hello"') end g.test_wkt_wrapper_bool = function() local s = pb.text.encode(hello.Event_descriptor, {is_admin = true}) t.assert_str_contains(s, 'is_admin {') t.assert_str_contains(s, 'value: true') end g.test_wkt_fieldmask = function() local s = pb.text.encode(hello.Event_descriptor, {update_mask = {'title', 'extension'}}) t.assert_str_contains(s, 'update_mask {') t.assert_str_contains(s, 'paths: "title"') t.assert_str_contains(s, 'paths: "extension"') end g.test_wkt_any_opaque = function() local s = pb.text.encode(hello.Event_descriptor, {extension = {type_url = 'type.googleapis.com/X', value = '\x01\x02'}}) t.assert_str_contains(s, 'extension {') t.assert_str_contains(s, 'type_url: "type.googleapis.com/X"') t.assert_str_contains(s, 'value: "\\001\\002"') end g.test_wkt_struct = function() local s = pb.text.encode(hello.Event_descriptor, {payload = {region = 'us-east-1'}}) t.assert_str_contains(s, 'payload {') t.assert_str_contains(s, 'fields {') t.assert_str_contains(s, 'key: "region"') t.assert_str_contains(s, 'string_value: "us-east-1"') end g.test_wkt_value_scalar = function() local s = pb.text.encode(hello.Event_descriptor, {attribute = 'hi'}) t.assert_str_contains(s, 'attribute {') t.assert_str_contains(s, 'string_value: "hi"') end g.test_wkt_value_null = function() local s = pb.text.encode(hello.Event_descriptor, {attribute = pb.NULL}) t.assert_str_contains(s, 'null_value: NULL_VALUE') end g.test_wkt_listvalue = function() local s = pb.text.encode(hello.Event_descriptor, {tags = pb.wkt.list({1, 'two', true})}) t.assert_str_contains(s, 'tags {') t.assert_str_contains(s, 'values {') t.assert_str_contains(s, 'number_value: 1.0') t.assert_str_contains(s, 'string_value: "two"') t.assert_str_contains(s, 'bool_value: true') end -- ---- top-level WKT (descriptor itself is a WKT) ------------------- g.test_top_level_timestamp = function() local s = pb.text.encode(pb.wkt.Timestamp_descriptor, {seconds = 100, nanos = 1}) t.assert_equals(s, 'seconds: 100\nnanos: 1\n') end g.test_top_level_fieldmask = function() local s = pb.text.encode(pb.wkt.FieldMask_descriptor, {'a', 'b'}) t.assert_equals(s, 'paths: "a"\npaths: "b"\n') end g.test_top_level_empty = function() local s = pb.text.encode(pb.wkt.Empty_descriptor, {}) t.assert_equals(s, '') end -- ---- generated codegen wrapper ------------------------------------ g.test_generated_text_wrapper = function() local s = hello.Address_text({street = 'X', zip = 1}) t.assert_equals(s, 'street: "X"\nzip: 1\n') s = hello.Address_text({street = 'X', zip = 1}, {single_line = true}) t.assert_equals(s, 'street: "X" zip: 1') end end