local upb = require "upb" local lunit = require "lunit" if _VERSION >= 'Lua 5.2' then _ENV = lunit.module("testupb", "seeall") else module("testupb", lunit.testcase, package.seeall) end function iter_to_array(iter) local arr = {} for v in iter do arr[#arr + 1] = v end return arr end function test_fielddef() local f = upb.FieldDef() assert_false(f:is_frozen()) assert_nil(f:number()) assert_nil(f:name()) assert_nil(f:type()) assert_equal(upb.LABEL_OPTIONAL, f:label()) f:set_name("foo_field") f:set_number(3) f:set_label(upb.LABEL_REPEATED) f:set_type(upb.TYPE_FLOAT) assert_equal("foo_field", f:name()) assert_equal(3, f:number()) assert_equal(upb.LABEL_REPEATED, f:label()) assert_equal(upb.TYPE_FLOAT, f:type()) local f2 = upb.FieldDef{ name = "foo", number = 5, type = upb.TYPE_DOUBLE, label = upb.LABEL_REQUIRED } assert_equal("foo", f2:name()) assert_equal(5, f2:number()) assert_equal(upb.TYPE_DOUBLE, f2:type()) assert_equal(upb.LABEL_REQUIRED, f2:label()) end function test_enumdef() local e = upb.EnumDef() assert_equal(0, #e) assert_nil(e:value(5)) assert_nil(e:value("NONEXISTENT_NAME")) for name, value in e:values() do fail() end e:add("VAL1", 1) e:add("VAL2", 2) local values = {} for name, value in e:values() do values[name] = value end assert_equal(1, values["VAL1"]) assert_equal(2, values["VAL2"]) local e2 = upb.EnumDef{ values = { {"FOO", 1}, {"BAR", 77}, } } assert_equal(1, e2:value("FOO")) assert_equal(77, e2:value("BAR")) assert_equal("FOO", e2:value(1)) assert_equal("BAR", e2:value(77)) e2:freeze() local f = upb.FieldDef{type = upb.TYPE_ENUM} -- No default set and no EnumDef to get a default from. assert_equal(f:default(), nil) f:set_subdef(upb.EnumDef()) -- No default to pull in from the EnumDef. assert_equal(f:default(), nil) f:set_subdef(e2) -- First member added to e2. assert_equal(f:default(), "FOO") f:set_subdef(nil) assert_equal(f:default(), nil) f:set_default(1) assert_equal(f:default(), 1) f:set_default("YOYOYO") assert_equal(f:default(), "YOYOYO") f:set_subdef(e2) f:set_default(1) -- It prefers to return a string, and could resolve the explicit "1" we set -- it to to the string value. assert_equal(f:default(), "FOO") -- FieldDef can specify default value by name or number, but the value must -- exist at freeze time. local m1 = upb.build_defs{ upb.MessageDef{ full_name = "A", fields = { upb.FieldDef{ name = "f1", number = 1, type = upb.TYPE_ENUM, subdef = e2, default = "BAR" }, upb.FieldDef{ name = "f2", number = 2, type = upb.TYPE_ENUM, subdef = e2, default = 77 } } } } assert_equal(m1:field("f1"):default(), "BAR") assert_equal(m1:field("f1"):default(), "BAR") assert_error_match( "enum default for field A.f1 .DOESNT_EXIST. is not in the enum", function() local m1 = upb.build_defs{ upb.MessageDef{ full_name = "A", fields = { upb.FieldDef{ name = "f1", number = 1, type = upb.TYPE_ENUM, subdef = e2, default = "DOESNT_EXIST" } } } } end ) assert_error_match( "enum default for field A.f1 .142. is not in the enum", function() local m1 = upb.build_defs{ upb.MessageDef{ full_name = "A", fields = { upb.FieldDef{ name = "f1", number = 1, type = upb.TYPE_ENUM, subdef = e2, default = 142 } } } } end ) end function test_empty_msgdef() local md = upb.MessageDef() assert_nil(md:full_name()) -- Def without name is anonymous. assert_false(md:is_frozen()) assert_equal(0, #md) assert_nil(md:field("nonexistent_field")) assert_nil(md:field(3)) for field in md:fields() do fail() end upb.freeze(md) assert_true(md:is_frozen()) assert_equal(0, #md) assert_nil(md:field("nonexistent_field")) assert_nil(md:field(3)) for field in md:fields() do fail() end end function test_msgdef_constructor() local f1 = upb.FieldDef{name = "field1", number = 7, type = upb.TYPE_INT32} local f2 = upb.FieldDef{name = "field2", number = 8, type = upb.TYPE_INT32} local md = upb.MessageDef{ full_name = "TestMessage", fields = {f1, f2} } assert_equal("TestMessage", md:full_name()) assert_false(md:is_frozen()) assert_equal(2, #md) assert_equal(f1, md:field("field1")) assert_equal(f2, md:field("field2")) assert_equal(f1, md:field(7)) assert_equal(f2, md:field(8)) local count = 0 local found = {} for field in md:fields() do count = count + 1 found[field] = true end assert_equal(2, count) assert_true(found[f1]) assert_true(found[f2]) upb.freeze(md) end function test_iteration() -- Test that we cannot crash the process even if we modify the set of fields -- during iteration. local md = upb.MessageDef{full_name = "TestMessage"} for i=1,10 do md:add(upb.FieldDef{ name = "field" .. tostring(i), number = 1000 - i, type = upb.TYPE_INT32 }) end local add = #md for f in md:fields() do if add > 0 then add = add - 1 for i=10000,11000 do local field_name = "field" .. tostring(i) -- We want to add fields to the table to trigger a table resize, -- but we must skip it if the field name or number already exists -- otherwise it will raise an error. if md:field(field_name) == nil and md:field(i) == nil then md:add(upb.FieldDef{ name = field_name, number = i, type = upb.TYPE_INT32 }) end end end end -- Test that iterators don't crash the process even if the MessageDef goes -- out of scope. -- -- Note: have previously verified that this can indeed crash the process if -- we do not explicitly add a reference from the iterator to the underlying -- MessageDef. local iter = md:fields() md = nil collectgarbage() while iter() do end local ed = upb.EnumDef{ values = { {"FOO", 1}, {"BAR", 77}, } } iter = ed:values() ed = nil collectgarbage() while iter() do end end function test_msgdef_setters() local md = upb.MessageDef() md:set_full_name("Message1") assert_equal("Message1", md:full_name()) local f = upb.FieldDef{name = "field1", number = 3, type = upb.TYPE_DOUBLE} md:add(f) assert_equal(1, #md) assert_equal(f, md:field("field1")) end function test_msgdef_errors() assert_error(function() upb.MessageDef{bad_initializer_key = 5} end) local md = upb.MessageDef() assert_error(function() -- Duplicate field number. upb.MessageDef{ fields = { upb.FieldDef{name = "field1", number = 1, type = upb.TYPE_INT32}, upb.FieldDef{name = "field2", number = 1, type = upb.TYPE_INT32} } } end) assert_error(function() -- Duplicate field name. upb.MessageDef{ fields = { upb.FieldDef{name = "field1", number = 1, type = upb.TYPE_INT32}, upb.FieldDef{name = "field1", number = 2, type = upb.TYPE_INT32} } } end) -- attempt to set a name with embedded NULLs. assert_error_match("names cannot have embedded NULLs", function() md:set_full_name("abc\0def") end) upb.freeze(md) -- Attempt to mutate frozen MessageDef. assert_error_match("frozen", function() md:add(upb.FieldDef{name = "field1", number = 1, type = upb.TYPE_INT32}) end) assert_error_match("frozen", function() md:set_full_name("abc") end) -- Attempt to freeze a msgdef without freezing its subdef. assert_error_match("is not frozen or being frozen", function() m1 = upb.MessageDef() upb.freeze( upb.MessageDef{ fields = { upb.FieldDef{name = "f1", number = 1, type = upb.TYPE_MESSAGE, subdef = m1} } } ) end) end function test_symtab() local empty = upb.SymbolTable() assert_equal(0, #iter_to_array(empty:defs(upb.DEF_ANY))) assert_equal(0, #iter_to_array(empty:defs(upb.DEF_MSG))) assert_equal(0, #iter_to_array(empty:defs(upb.DEF_ENUM))) local symtab = upb.SymbolTable{ upb.MessageDef{full_name = "TestMessage"}, upb.MessageDef{full_name = "ContainingMessage", fields = { upb.FieldDef{name = "field1", number = 1, type = upb.TYPE_INT32}, upb.FieldDef{name = "field2", number = 2, type = upb.TYPE_MESSAGE, subdef_name = ".TestMessage"} } } } local msgdef1 = symtab:lookup("TestMessage") local msgdef2 = symtab:lookup("ContainingMessage") assert_not_nil(msgdef1) assert_not_nil(msgdef2) assert_equal(msgdef1, msgdef2:field("field2"):subdef()) assert_true(msgdef1:is_frozen()) assert_true(msgdef2:is_frozen()) symtab:add{ upb.MessageDef{full_name = "ContainingMessage2", fields = { upb.FieldDef{name = "field5", number = 5, type = upb.TYPE_MESSAGE, subdef = msgdef2} } } } local msgdef3 = symtab:lookup("ContainingMessage2") assert_not_nil(msgdef3) assert_equal(msgdef3:field("field5"):subdef(), msgdef2) -- Freeze the symtab and verify that mutating operations are not allowed. assert_false(symtab:is_frozen()) symtab:freeze() assert_true(symtab:is_frozen()) assert_error_match("frozen", function() symtab:freeze() end) assert_error_match("frozen", function() symtab:add{ upb.MessageDef{full_name = "Foo"} } end) end function test_symtab_add_extension() -- Adding an extension at the same time as the extendee. local symtab = upb.SymbolTable{ upb.MessageDef{full_name = "M1"}, upb.FieldDef{name = "extension1", is_extension = true, number = 1, type = upb.TYPE_INT32, containing_type_name = "M1"} } local m1 = symtab:lookup("M1") assert_not_nil(m1) assert_equal(1, #m1) local f1 = m1:field("extension1") assert_not_nil(f1) assert_true(f1:is_extension()) assert_true(f1:is_frozen()) assert_equal(1, f1:number()) -- Adding an extension to an existing extendee. symtab:add{ upb.FieldDef{name = "extension2", is_extension = true, number = 2, type = upb.TYPE_INT32, containing_type_name = "M1"} } local m1_2 = symtab:lookup("M1") assert_not_nil(m1_2) assert_true(m1 ~= m1_2) assert_equal(2, #m1_2) local f2 = m1_2:field("extension2") assert_not_nil(f2) assert_true(f2:is_extension()) assert_true(f2:is_frozen()) assert_equal(2, f2:number()) end function test_numeric_array() local function test_for_numeric_type(upb_type, val, too_big, too_small, bad3) local array = upb.Array(upb_type) assert_equal(0, #array) -- 0 is never a valid index in Lua. assert_error_match("array index", function() return array[0] end) -- Past the end of the array. assert_error_match("array index", function() return array[1] end) array[1] = val assert_equal(val, array[1]) assert_equal(1, #array) -- Past the end of the array. assert_error_match("array index", function() return array[2] end) array[2] = 10 assert_equal(val, array[1]) assert_equal(10, array[2]) assert_equal(2, #array) -- Past the end of the array. assert_error_match("array index", function() return array[3] end) local n = 1 for i, val in upb.ipairs(array) do assert_equal(n, i) n = n + 1 assert_equal(array[i], val) end -- Values that are out of range. local errmsg = "not an integer or out of range" if too_small then assert_error_match(errmsg, function() array[3] = too_small end) end if too_big then assert_error_match(errmsg, function() array[3] = too_big end) end if bad3 then assert_error_match(errmsg, function() array[3] = bad3 end) end -- Can't assign other Lua types. errmsg = "bad argument #3" assert_error_match(errmsg, function() array[3] = "abc" end) assert_error_match(errmsg, function() array[3] = true end) assert_error_match(errmsg, function() array[3] = false end) assert_error_match(errmsg, function() array[3] = nil end) assert_error_match(errmsg, function() array[3] = {} end) assert_error_match(errmsg, function() array[3] = print end) assert_error_match(errmsg, function() array[3] = array end) end -- in-range of 64-bit types but not exactly representable as double local bad64 = 2^68 - 1 test_for_numeric_type(upb.TYPE_UINT32, 2^32 - 1, 2^32, -1, 5.1) test_for_numeric_type(upb.TYPE_UINT64, 2^63, 2^64, -1, bad64) test_for_numeric_type(upb.TYPE_INT32, 2^31 - 1, 2^31, -2^31 - 1, 5.1) -- Enums don't exist at a language level in Lua, so we just represent enum -- values as int32s. test_for_numeric_type(upb.TYPE_ENUM, 2^31 - 1, 2^31, -2^31 - 1, 5.1) test_for_numeric_type(upb.TYPE_INT64, 2^62, 2^63, -2^64, bad64) test_for_numeric_type(upb.TYPE_FLOAT, 10^38) test_for_numeric_type(upb.TYPE_DOUBLE, 10^101) end function test_string_array() local function test_for_string_type(upb_type) local array = upb.Array(upb_type) assert_equal(0, #array) -- 0 is never a valid index in Lua. assert_error_match("array index", function() return array[0] end) -- Past the end of the array. assert_error_match("array index", function() return array[1] end) array[1] = "foo" assert_equal("foo", array[1]) assert_equal(1, #array) -- Past the end of the array. assert_error_match("array index", function() return array[2] end) local array2 = upb.Array(upb_type) assert_equal(0, #array2) array[2] = "bar" assert_equal("foo", array[1]) assert_equal("bar", array[2]) assert_equal(2, #array) -- Past the end of the array. assert_error_match("array index", function() return array[3] end) local n = 1 for i, val in upb.ipairs(array) do assert_equal(n, i) n = n + 1 assert_equal(array[i], val) end assert_equal(3, n) -- Can't assign other Lua types. assert_error_match("Expected string", function() array[3] = 123 end) assert_error_match("Expected string", function() array[3] = true end) assert_error_match("Expected string", function() array[3] = false end) assert_error_match("Expected string", function() array[3] = nil end) assert_error_match("Expected string", function() array[3] = {} end) assert_error_match("Expected string", function() array[3] = print end) assert_error_match("Expected string", function() array[3] = array end) end test_for_string_type(upb.TYPE_STRING) test_for_string_type(upb.TYPE_BYTES) end function test_msg_primitives() local function test_for_numeric_type(upb_type, val, too_big, too_small, bad3) msg = upb.Message( upb.build_defs{ upb.MessageDef{full_name = "TestMessage", fields = { upb.FieldDef{name = "f", number = 1, type = upb_type}, } } } ) -- Defaults to nil assert_nil(msg.f) msg.f = 0 assert_equal(0, msg.f) msg.f = val assert_equal(val, msg.f) local errmsg = "not an integer or out of range" if too_small then assert_error_match(errmsg, function() msg.f = too_small end) end if too_big then assert_error_match(errmsg, function() msg.f = too_big end) end if bad3 then assert_error_match(errmsg, function() msg.f = bad3 end) end -- Can't assign other Lua types. errmsg = "bad argument #3" assert_error_match(errmsg, function() msg.f = "abc" end) assert_error_match(errmsg, function() msg.f = true end) assert_error_match(errmsg, function() msg.f = false end) assert_error_match(errmsg, function() msg.f = nil end) assert_error_match(errmsg, function() msg.f = {} end) assert_error_match(errmsg, function() msg.f = print end) assert_error_match(errmsg, function() msg.f = array end) end local msgdef = upb.build_defs{ upb.MessageDef{full_name = "TestMessage", fields = { upb.FieldDef{name = "i32", number = 1, type = upb.TYPE_INT32}, upb.FieldDef{name = "u32", number = 2, type = upb.TYPE_UINT32}, upb.FieldDef{name = "i64", number = 3, type = upb.TYPE_INT64}, upb.FieldDef{name = "u64", number = 4, type = upb.TYPE_UINT64}, upb.FieldDef{name = "dbl", number = 5, type = upb.TYPE_DOUBLE}, upb.FieldDef{name = "flt", number = 6, type = upb.TYPE_FLOAT}, upb.FieldDef{name = "bool", number = 7, type = upb.TYPE_BOOL}, } } } msg = upb.Message(msgdef) -- Unset member returns nil. This is unlike C++/Java, but is more -- Lua-like behavior. assert_equal(nil, msg.i32) assert_equal(nil, msg.u32) assert_equal(nil, msg.i64) assert_equal(nil, msg.u64) assert_equal(nil, msg.dbl) assert_equal(nil, msg.flt) assert_equal(nil, msg.bool) -- Attempts to access non-existent fields fail. assert_error_match("no such field", function() msg.no_such = 1 end) msg.i32 = 10 msg.u32 = 20 msg.i64 = 30 msg.u64 = 40 msg.dbl = 50 msg.flt = 60 msg.bool = true assert_equal(10, msg.i32) assert_equal(20, msg.u32) assert_equal(30, msg.i64) assert_equal(40, msg.u64) assert_equal(50, msg.dbl) assert_equal(60, msg.flt) assert_equal(true, msg.bool) test_for_numeric_type(upb.TYPE_UINT32, 2^32 - 1, 2^32, -1, 5.1) test_for_numeric_type(upb.TYPE_UINT64, 2^62, 2^64, -1, bad64) test_for_numeric_type(upb.TYPE_INT32, 2^31 - 1, 2^31, -2^31 - 1, 5.1) test_for_numeric_type(upb.TYPE_INT64, 2^61, 2^63, -2^64, bad64) test_for_numeric_type(upb.TYPE_FLOAT, 2^20) test_for_numeric_type(upb.TYPE_DOUBLE, 10^101) end function test_msg_array() local msgdef = upb.build_defs{ upb.MessageDef{full_name = "TestMessage", fields = { upb.FieldDef{name = "i32_array", number = 1, type = upb.TYPE_INT32, label = upb.LABEL_REPEATED}, } } } msg = upb.Message(msgdef) assert_nil(msg.i32_array) -- Can't assign a scalar; array is expected. assert_error_match("lupb.array expected", function() msg.i32_array = 5 end) -- Can't assign array of the wrong type. local function assign_int64() msg.i32_array = upb.Array(upb.TYPE_INT64) end assert_error_match("Array type mismatch", assign_int64) local arr = upb.Array(upb.TYPE_INT32) msg.i32_array = arr assert_equal(arr, msg.i32_array) -- Can't assign other Lua types. assert_error_match("array expected", function() msg.i32_array = "abc" end) assert_error_match("array expected", function() msg.i32_array = true end) assert_error_match("array expected", function() msg.i32_array = false end) assert_error_match("array expected", function() msg.i32_array = nil end) assert_error_match("array expected", function() msg.i32_array = {} end) assert_error_match("array expected", function() msg.i32_array = print end) end function test_msg_submsg() local test_msgdef, submsg_msgdef = upb.build_defs{ upb.MessageDef{full_name = "TestMessage", fields = { upb.FieldDef{name = "submsg", number = 1, type = upb.TYPE_MESSAGE, subdef_name = ".SubMessage"}, } }, upb.MessageDef{full_name = "SubMessage"} } msg = upb.Message(test_msgdef) assert_nil(msg.submsg) -- Can't assign message of the wrong type. local function assign_int64() msg.submsg = upb.Message(test_msgdef) end assert_error_match("Message type mismatch", assign_int64) local sub = upb.Message(submsg_msgdef) msg.submsg = sub assert_equal(sub, msg.submsg) -- Can't assign other Lua types. assert_error_match("msg expected", function() msg.submsg = "abc" end) assert_error_match("msg expected", function() msg.submsg = true end) assert_error_match("msg expected", function() msg.submsg = false end) assert_error_match("msg expected", function() msg.submsg = nil end) assert_error_match("msg expected", function() msg.submsg = {} end) assert_error_match("msg expected", function() msg.submsg = print end) end -- Lua 5.1 and 5.2 have slightly different semantics for how a finalizer -- can be defined in Lua. if _VERSION >= 'Lua 5.2' then function defer(fn) setmetatable({}, { __gc = fn }) end else function defer(fn) getmetatable(newproxy(true)).__gc = fn end end function test_finalizer() -- Tests that we correctly handle a call into an already-finalized object. -- Collectible objects are finalized in the opposite order of creation. do local t = {} defer(function() assert_error_match("called into dead object", function() -- Generic def call. t[1]:full_name() end) assert_error_match("called into dead object", function() -- Specific msgdef call. t[1]:add() end) assert_error_match("called into dead object", function() t[2]:values() end) assert_error_match("called into dead object", function() t[3]:number() end) assert_error_match("called into dead object", function() t[4]:lookup() end) end) t = { upb.MessageDef(), upb.EnumDef(), upb.FieldDef(), upb.SymbolTable(), } end collectgarbage() end local stats = lunit.main() if stats.failed > 0 or stats.errors > 0 then error("One or more errors in test suite") end