From fd1a3ff11d5854c34ba66c63598cdc5fd234e399 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Tue, 6 Jan 2015 15:44:09 -0800 Subject: Support for maps in the MRI C Ruby extension. This adds the Map container and support for parsing and serializing maps in the protobuf wire format (as defined by the C++ implementation, with MapEntry submessages in a repeated field). JSON map serialization/parsing are not yet supported as these will require some changes to upb as well. --- ruby/tests/basic.rb | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 256 insertions(+), 2 deletions(-) (limited to 'ruby/tests/basic.rb') diff --git a/ruby/tests/basic.rb b/ruby/tests/basic.rb index 05b3a0fa..2620fa9b 100644 --- a/ruby/tests/basic.rb +++ b/ruby/tests/basic.rb @@ -36,23 +36,43 @@ module BasicTest add_message "TestMessage2" do optional :foo, :int32, 1 end + add_message "Recursive1" do optional :foo, :message, 1, "Recursive2" end add_message "Recursive2" do optional :foo, :message, 1, "Recursive1" end + add_enum "TestEnum" do value :Default, 0 value :A, 1 value :B, 2 value :C, 3 end + add_message "BadFieldNames" do optional :dup, :int32, 1 optional :class, :int32, 2 optional :"a.b", :int32, 3 end + + add_message "MapMessage" do + map :map_string_int32, :string, :int32, 1 + map :map_string_msg, :string, :message, 2, "TestMessage2" + end + add_message "MapMessageWireEquiv" do + repeated :map_string_int32, :message, 1, "MapMessageWireEquiv_entry1" + repeated :map_string_msg, :message, 2, "MapMessageWireEquiv_entry2" + end + add_message "MapMessageWireEquiv_entry1" do + optional :key, :string, 1 + optional :value, :int32, 2 + end + add_message "MapMessageWireEquiv_entry2" do + optional :key, :string, 1 + optional :value, :message, 2, "TestMessage2" + end end TestMessage = pool.lookup("TestMessage").msgclass @@ -61,6 +81,12 @@ module BasicTest Recursive2 = pool.lookup("Recursive2").msgclass TestEnum = pool.lookup("TestEnum").enummodule BadFieldNames = pool.lookup("BadFieldNames").msgclass + MapMessage = pool.lookup("MapMessage").msgclass + MapMessageWireEquiv = pool.lookup("MapMessageWireEquiv").msgclass + MapMessageWireEquiv_entry1 = + pool.lookup("MapMessageWireEquiv_entry1").msgclass + MapMessageWireEquiv_entry2 = + pool.lookup("MapMessageWireEquiv_entry2").msgclass # ------------ test cases --------------- @@ -300,7 +326,7 @@ module BasicTest l.push :B l.push :C assert l.count == 3 - assert_raise NameError do + assert_raise RangeError do l.push :D end assert l[0] == :A @@ -324,12 +350,240 @@ module BasicTest end end + def test_map_basic + # allowed key types: + # :int32, :int64, :uint32, :uint64, :bool, :string, :bytes. + + m = Google::Protobuf::Map.new(:string, :int32) + m["asdf"] = 1 + assert m["asdf"] == 1 + m["jkl;"] = 42 + assert m == { "jkl;" => 42, "asdf" => 1 } + assert m.has_key?("asdf") + assert !m.has_key?("qwerty") + assert m.length == 2 + + m2 = m.dup + assert m == m2 + assert m.hash != 0 + assert m.hash == m2.hash + + collected = {} + m.each { |k,v| collected[v] = k } + assert collected == { 42 => "jkl;", 1 => "asdf" } + + assert m.delete("asdf") == 1 + assert !m.has_key?("asdf") + assert m["asdf"] == nil + assert !m.has_key?("asdf") + + # We only assert on inspect value when there is one map entry because the + # order in which elements appear is unspecified (depends on the internal + # hash function). We don't want a brittle test. + assert m.inspect == "{\"jkl;\" => 42}" + + assert m.keys == ["jkl;"] + assert m.values == [42] + + m.clear + assert m.length == 0 + assert m == {} + + assert_raise TypeError do + m[1] = 1 + end + assert_raise RangeError do + m["asdf"] = 0x1_0000_0000 + end + end + + def test_map_ctor + m = Google::Protobuf::Map.new(:string, :int32, + {"a" => 1, "b" => 2, "c" => 3}) + assert m == {"a" => 1, "c" => 3, "b" => 2} + end + + def test_map_keytypes + m = Google::Protobuf::Map.new(:int32, :int32) + m[1] = 42 + m[-1] = 42 + assert_raise RangeError do + m[0x8000_0000] = 1 + end + assert_raise TypeError do + m["asdf"] = 1 + end + + m = Google::Protobuf::Map.new(:int64, :int32) + m[0x1000_0000_0000_0000] = 1 + assert_raise RangeError do + m[0x1_0000_0000_0000_0000] = 1 + end + assert_raise TypeError do + m["asdf"] = 1 + end + + m = Google::Protobuf::Map.new(:uint32, :int32) + m[0x8000_0000] = 1 + assert_raise RangeError do + m[0x1_0000_0000] = 1 + end + assert_raise RangeError do + m[-1] = 1 + end + + m = Google::Protobuf::Map.new(:uint64, :int32) + m[0x8000_0000_0000_0000] = 1 + assert_raise RangeError do + m[0x1_0000_0000_0000_0000] = 1 + end + assert_raise RangeError do + m[-1] = 1 + end + + m = Google::Protobuf::Map.new(:bool, :int32) + m[true] = 1 + m[false] = 2 + assert_raise TypeError do + m[1] = 1 + end + assert_raise TypeError do + m["asdf"] = 1 + end + + m = Google::Protobuf::Map.new(:string, :int32) + m["asdf"] = 1 + assert_raise TypeError do + m[1] = 1 + end + assert_raise TypeError do + bytestring = ["FFFF"].pack("H*") + m[bytestring] = 1 + end + + m = Google::Protobuf::Map.new(:bytes, :int32) + bytestring = ["FFFF"].pack("H*") + m[bytestring] = 1 + assert_raise TypeError do + m["asdf"] = 1 + end + assert_raise TypeError do + m[1] = 1 + end + end + + def test_map_msg_enum_valuetypes + m = Google::Protobuf::Map.new(:string, :message, TestMessage) + m["asdf"] = TestMessage.new + assert_raise TypeError do + m["jkl;"] = TestMessage2.new + end + + m = Google::Protobuf::Map.new(:string, :message, TestMessage, + { "a" => TestMessage.new(:optional_int32 => 42), + "b" => TestMessage.new(:optional_int32 => 84) }) + assert m.length == 2 + assert m.values.map{|msg| msg.optional_int32}.sort == [42, 84] + + m = Google::Protobuf::Map.new(:string, :enum, TestEnum, + { "x" => :A, "y" => :B, "z" => :C }) + assert m.length == 3 + assert m["z"] == :C + m["z"] = 2 + assert m["z"] == :B + m["z"] = 4 + assert m["z"] == 4 + assert_raise RangeError do + m["z"] = :Z + end + assert_raise TypeError do + m["z"] = "z" + end + end + + def test_map_dup_deep_copy + m = Google::Protobuf::Map.new(:string, :message, TestMessage, + { "a" => TestMessage.new(:optional_int32 => 42), + "b" => TestMessage.new(:optional_int32 => 84) }) + + m2 = m.dup + assert m == m2 + assert m.object_id != m2.object_id + assert m["a"].object_id == m2["a"].object_id + assert m["b"].object_id == m2["b"].object_id + + m2 = Google::Protobuf.deep_copy(m) + assert m == m2 + assert m.object_id != m2.object_id + assert m["a"].object_id != m2["a"].object_id + assert m["b"].object_id != m2["b"].object_id + end + + def test_map_field + m = MapMessage.new + assert m.map_string_int32 == {} + assert m.map_string_msg == {} + + m = MapMessage.new(:map_string_int32 => {"a" => 1, "b" => 2}, + :map_string_msg => {"a" => TestMessage2.new(:foo => 1), + "b" => TestMessage2.new(:foo => 2)}) + assert m.map_string_int32.keys.sort == ["a", "b"] + assert m.map_string_int32["a"] == 1 + assert m.map_string_msg["b"].foo == 2 + + m.map_string_int32["c"] = 3 + assert m.map_string_int32["c"] == 3 + m.map_string_msg["c"] = TestMessage2.new(:foo => 3) + assert m.map_string_msg["c"] == TestMessage2.new(:foo => 3) + m.map_string_msg.delete("b") + m.map_string_msg.delete("c") + assert m.map_string_msg == { "a" => TestMessage2.new(:foo => 1) } + + assert_raise TypeError do + m.map_string_msg["e"] = TestMessage.new # wrong value type + end + # ensure nothing was added by the above + assert m.map_string_msg == { "a" => TestMessage2.new(:foo => 1) } + + m.map_string_int32 = Google::Protobuf::Map.new(:string, :int32) + assert_raise TypeError do + m.map_string_int32 = Google::Protobuf::Map.new(:string, :int64) + end + assert_raise TypeError do + m.map_string_int32 = {} + end + + assert_raise TypeError do + m = MapMessage.new(:map_string_int32 => { 1 => "I am not a number" }) + end + end + + def test_map_encode_decode + m = MapMessage.new(:map_string_int32 => {"a" => 1, "b" => 2}, + :map_string_msg => {"a" => TestMessage2.new(:foo => 1), + "b" => TestMessage2.new(:foo => 2)}) + m2 = MapMessage.decode(MapMessage.encode(m)) + assert m == m2 + + m3 = MapMessageWireEquiv.decode(MapMessage.encode(m)) + assert m3.map_string_int32.length == 2 + + kv = {} + m3.map_string_int32.map { |msg| kv[msg.key] = msg.value } + assert kv == {"a" => 1, "b" => 2} + + kv = {} + m3.map_string_msg.map { |msg| kv[msg.key] = msg.value } + assert kv == {"a" => TestMessage2.new(:foo => 1), + "b" => TestMessage2.new(:foo => 2)} + end + def test_enum_field m = TestMessage.new assert m.optional_enum == :Default m.optional_enum = :A assert m.optional_enum == :A - assert_raise NameError do + assert_raise RangeError do m.optional_enum = :ASDF end m.optional_enum = 1 -- cgit v1.2.3