• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/ruby
2
3# basic_test_pb.rb is in the same directory as this test.
4$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
5
6require 'basic_test_pb'
7require 'common_tests'
8require 'google/protobuf'
9require 'json'
10require 'test/unit'
11
12# ------------- generated code --------------
13
14module BasicTest
15  pool = Google::Protobuf::DescriptorPool.new
16  pool.build do
17    add_message "BadFieldNames" do
18      optional :dup, :int32, 1
19      optional :class, :int32, 2
20      optional :"a.b", :int32, 3
21    end
22  end
23
24  BadFieldNames = pool.lookup("BadFieldNames").msgclass
25
26# ------------ test cases ---------------
27
28  class MessageContainerTest < Test::Unit::TestCase
29    # Required by CommonTests module to resolve proto3 proto classes used in tests.
30    def proto_module
31      ::BasicTest
32    end
33    include CommonTests
34
35    def test_has_field
36      m = TestMessage.new
37      assert !m.has_optional_msg?
38      m.optional_msg = TestMessage2.new
39      assert m.has_optional_msg?
40      assert TestMessage.descriptor.lookup('optional_msg').has?(m)
41
42      m = OneofMessage.new
43      assert !m.has_my_oneof?
44      m.a = "foo"
45      assert m.has_my_oneof?
46      assert_raise NoMethodError do
47        m.has_a?
48      end
49      assert_raise ArgumentError do
50        OneofMessage.descriptor.lookup('a').has?(m)
51      end
52
53      m = TestMessage.new
54      assert_raise NoMethodError do
55        m.has_optional_int32?
56      end
57      assert_raise ArgumentError do
58        TestMessage.descriptor.lookup('optional_int32').has?(m)
59      end
60
61      assert_raise NoMethodError do
62        m.has_optional_string?
63      end
64      assert_raise ArgumentError do
65        TestMessage.descriptor.lookup('optional_string').has?(m)
66      end
67
68      assert_raise NoMethodError do
69        m.has_optional_bool?
70      end
71      assert_raise ArgumentError do
72        TestMessage.descriptor.lookup('optional_bool').has?(m)
73      end
74
75      assert_raise NoMethodError do
76        m.has_repeated_msg?
77      end
78      assert_raise ArgumentError do
79        TestMessage.descriptor.lookup('repeated_msg').has?(m)
80      end
81    end
82
83    def test_set_clear_defaults
84      m = TestMessage.new
85
86      m.optional_int32 = -42
87      assert_equal -42, m.optional_int32
88      m.clear_optional_int32
89      assert_equal 0, m.optional_int32
90
91      m.optional_int32 = 50
92      assert_equal 50, m.optional_int32
93      TestMessage.descriptor.lookup('optional_int32').clear(m)
94      assert_equal 0, m.optional_int32
95
96      m.optional_string = "foo bar"
97      assert_equal "foo bar", m.optional_string
98      m.clear_optional_string
99      assert_equal "", m.optional_string
100
101      m.optional_string = "foo"
102      assert_equal "foo", m.optional_string
103      TestMessage.descriptor.lookup('optional_string').clear(m)
104      assert_equal "", m.optional_string
105
106      m.optional_msg = TestMessage2.new(:foo => 42)
107      assert_equal TestMessage2.new(:foo => 42), m.optional_msg
108      assert m.has_optional_msg?
109      m.clear_optional_msg
110      assert_equal nil, m.optional_msg
111      assert !m.has_optional_msg?
112
113      m.optional_msg = TestMessage2.new(:foo => 42)
114      assert_equal TestMessage2.new(:foo => 42), m.optional_msg
115      TestMessage.descriptor.lookup('optional_msg').clear(m)
116      assert_equal nil, m.optional_msg
117
118      m.repeated_int32.push(1)
119      assert_equal [1], m.repeated_int32
120      m.clear_repeated_int32
121      assert_equal [], m.repeated_int32
122
123      m.repeated_int32.push(1)
124      assert_equal [1], m.repeated_int32
125      TestMessage.descriptor.lookup('repeated_int32').clear(m)
126      assert_equal [], m.repeated_int32
127
128      m = OneofMessage.new
129      m.a = "foo"
130      assert_equal "foo", m.a
131      assert m.has_my_oneof?
132      m.clear_a
133      assert !m.has_my_oneof?
134
135      m.a = "foobar"
136      assert m.has_my_oneof?
137      m.clear_my_oneof
138      assert !m.has_my_oneof?
139
140      m.a = "bar"
141      assert_equal "bar", m.a
142      assert m.has_my_oneof?
143      OneofMessage.descriptor.lookup('a').clear(m)
144      assert !m.has_my_oneof?
145    end
146
147
148    def test_initialization_map_errors
149      e = assert_raise ArgumentError do
150        TestMessage.new(:hello => "world")
151      end
152      assert_match(/hello/, e.message)
153
154      e = assert_raise ArgumentError do
155        MapMessage.new(:map_string_int32 => "hello")
156      end
157      assert_equal e.message, "Expected Hash object as initializer value for map field 'map_string_int32' (given String)."
158
159      e = assert_raise ArgumentError do
160        TestMessage.new(:repeated_uint32 => "hello")
161      end
162      assert_equal e.message, "Expected array as initializer value for repeated field 'repeated_uint32' (given String)."
163    end
164
165    def test_map_field
166      m = MapMessage.new
167      assert m.map_string_int32 == {}
168      assert m.map_string_msg == {}
169
170      m = MapMessage.new(
171        :map_string_int32 => {"a" => 1, "b" => 2},
172        :map_string_msg => {"a" => TestMessage2.new(:foo => 1),
173                            "b" => TestMessage2.new(:foo => 2)},
174        :map_string_enum => {"a" => :A, "b" => :B})
175      assert m.map_string_int32.keys.sort == ["a", "b"]
176      assert m.map_string_int32["a"] == 1
177      assert m.map_string_msg["b"].foo == 2
178      assert m.map_string_enum["a"] == :A
179
180      m.map_string_int32["c"] = 3
181      assert m.map_string_int32["c"] == 3
182      m.map_string_msg["c"] = TestMessage2.new(:foo => 3)
183      assert m.map_string_msg["c"] == TestMessage2.new(:foo => 3)
184      m.map_string_msg.delete("b")
185      m.map_string_msg.delete("c")
186      assert m.map_string_msg == { "a" => TestMessage2.new(:foo => 1) }
187
188      assert_raise Google::Protobuf::TypeError do
189        m.map_string_msg["e"] = TestMessage.new # wrong value type
190      end
191      # ensure nothing was added by the above
192      assert m.map_string_msg == { "a" => TestMessage2.new(:foo => 1) }
193
194      m.map_string_int32 = Google::Protobuf::Map.new(:string, :int32)
195      assert_raise Google::Protobuf::TypeError do
196        m.map_string_int32 = Google::Protobuf::Map.new(:string, :int64)
197      end
198      assert_raise Google::Protobuf::TypeError do
199        m.map_string_int32 = {}
200      end
201
202      assert_raise TypeError do
203        m = MapMessage.new(:map_string_int32 => { 1 => "I am not a number" })
204      end
205    end
206
207    def test_map_inspect
208      m = MapMessage.new(
209        :map_string_int32 => {"a" => 1, "b" => 2},
210        :map_string_msg => {"a" => TestMessage2.new(:foo => 1),
211                            "b" => TestMessage2.new(:foo => 2)},
212        :map_string_enum => {"a" => :A, "b" => :B})
213      expected = "<BasicTest::MapMessage: map_string_int32: {\"b\"=>2, \"a\"=>1}, map_string_msg: {\"b\"=><BasicTest::TestMessage2: foo: 2>, \"a\"=><BasicTest::TestMessage2: foo: 1>}, map_string_enum: {\"b\"=>:B, \"a\"=>:A}>"
214      assert_equal expected, m.inspect
215    end
216
217    def test_map_corruption
218      # This pattern led to a crash in a previous version of upb/protobuf.
219      m = MapMessage.new(map_string_int32: { "aaa" => 1 })
220      m.map_string_int32['podid'] = 2
221      m.map_string_int32['aaa'] = 3
222    end
223
224    def test_concurrent_decoding
225      o = Outer.new
226      o.items[0] = Inner.new
227      raw = Outer.encode(o)
228
229      thds = 2.times.map do
230        Thread.new do
231          100000.times do
232            assert_equal o, Outer.decode(raw)
233          end
234        end
235      end
236      thds.map(&:join)
237    end
238
239    def test_map_encode_decode
240      m = MapMessage.new(
241        :map_string_int32 => {"a" => 1, "b" => 2},
242        :map_string_msg => {"a" => TestMessage2.new(:foo => 1),
243                            "b" => TestMessage2.new(:foo => 2)},
244        :map_string_enum => {"a" => :A, "b" => :B})
245      m2 = MapMessage.decode(MapMessage.encode(m))
246      assert m == m2
247
248      m3 = MapMessageWireEquiv.decode(MapMessage.encode(m))
249      assert m3.map_string_int32.length == 2
250
251      kv = {}
252      m3.map_string_int32.map { |msg| kv[msg.key] = msg.value }
253      assert kv == {"a" => 1, "b" => 2}
254
255      kv = {}
256      m3.map_string_msg.map { |msg| kv[msg.key] = msg.value }
257      assert kv == {"a" => TestMessage2.new(:foo => 1),
258                    "b" => TestMessage2.new(:foo => 2)}
259    end
260
261    def test_protobuf_decode_json_ignore_unknown_fields
262      m = TestMessage.decode_json({
263        optional_string: "foo",
264        not_in_message: "some_value"
265      }.to_json, { ignore_unknown_fields: true })
266
267      assert_equal m.optional_string, "foo"
268      e = assert_raise Google::Protobuf::ParseError do
269        TestMessage.decode_json({ not_in_message: "some_value" }.to_json)
270      end
271      assert_match(/No such field: not_in_message/, e.message)
272    end
273
274    def test_to_h
275      m = TestMessage.new(:optional_bool => true, :optional_double => -10.100001, :optional_string => 'foo', :repeated_string => ['bar1', 'bar2'], :repeated_msg => [TestMessage2.new(:foo => 100)])
276      expected_result = {
277        :optional_bool=>true,
278        :optional_bytes=>"",
279        :optional_double=>-10.100001,
280        :optional_enum=>:Default,
281        :optional_float=>0.0,
282        :optional_int32=>0,
283        :optional_int64=>0,
284        :optional_msg=>nil,
285        :optional_string=>"foo",
286        :optional_uint32=>0,
287        :optional_uint64=>0,
288        :repeated_bool=>[],
289        :repeated_bytes=>[],
290        :repeated_double=>[],
291        :repeated_enum=>[],
292        :repeated_float=>[],
293        :repeated_int32=>[],
294        :repeated_int64=>[],
295        :repeated_msg=>[{:foo => 100}],
296        :repeated_string=>["bar1", "bar2"],
297        :repeated_uint32=>[],
298        :repeated_uint64=>[]
299      }
300      assert_equal expected_result, m.to_h
301
302      m = MapMessage.new(
303        :map_string_int32 => {"a" => 1, "b" => 2},
304        :map_string_msg => {"a" => TestMessage2.new(:foo => 1),
305                            "b" => TestMessage2.new(:foo => 2)},
306        :map_string_enum => {"a" => :A, "b" => :B})
307      expected_result = {
308        :map_string_int32 => {"a" => 1, "b" => 2},
309        :map_string_msg => {"a" => {:foo => 1}, "b" => {:foo => 2}},
310        :map_string_enum => {"a" => :A, "b" => :B}
311      }
312      assert_equal expected_result, m.to_h
313    end
314
315
316    def test_json_maps
317      # TODO: Fix JSON in JRuby version.
318      return if RUBY_PLATFORM == "java"
319      m = MapMessage.new(:map_string_int32 => {"a" => 1})
320      expected = {mapStringInt32: {a: 1}, mapStringMsg: {}, mapStringEnum: {}}
321      expected_preserve = {map_string_int32: {a: 1}, map_string_msg: {}, map_string_enum: {}}
322      assert_equal JSON.parse(MapMessage.encode_json(m), :symbolize_names => true), expected
323
324      json = MapMessage.encode_json(m, :preserve_proto_fieldnames => true)
325      assert_equal JSON.parse(json, :symbolize_names => true), expected_preserve
326
327      m2 = MapMessage.decode_json(MapMessage.encode_json(m))
328      assert_equal m, m2
329    end
330
331    def test_json_maps_emit_defaults_submsg
332      # TODO: Fix JSON in JRuby version.
333      return if RUBY_PLATFORM == "java"
334      m = MapMessage.new(:map_string_msg => {"a" => TestMessage2.new})
335      expected = {mapStringInt32: {}, mapStringMsg: {a: {foo: 0}}, mapStringEnum: {}}
336
337      actual = MapMessage.encode_json(m, :emit_defaults => true)
338
339      assert_equal JSON.parse(actual, :symbolize_names => true), expected
340    end
341
342    def test_respond_to
343      # This test fails with JRuby 1.7.23, likely because of an old JRuby bug.
344      return if RUBY_PLATFORM == "java"
345      msg = MapMessage.new
346      assert msg.respond_to?(:map_string_int32)
347      assert !msg.respond_to?(:bacon)
348    end
349
350    def test_file_descriptor
351      file_descriptor = TestMessage.descriptor.file_descriptor
352      assert nil != file_descriptor
353      assert_equal "tests/basic_test.proto", file_descriptor.name
354      assert_equal :proto3, file_descriptor.syntax
355
356      file_descriptor = TestEnum.descriptor.file_descriptor
357      assert nil != file_descriptor
358      assert_equal "tests/basic_test.proto", file_descriptor.name
359      assert_equal :proto3, file_descriptor.syntax
360
361      file_descriptor = BadFieldNames.descriptor.file_descriptor
362      assert nil != file_descriptor
363      assert_equal nil, file_descriptor.name
364      assert_equal :proto3, file_descriptor.syntax
365    end
366
367    # Ruby 2.5 changed to raise FrozenError instead of RuntimeError
368    FrozenErrorType = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5') ? RuntimeError : FrozenError
369
370    def test_map_freeze
371      m = proto_module::MapMessage.new
372      m.map_string_int32['a'] = 5
373      m.map_string_msg['b'] = proto_module::TestMessage2.new
374
375      m.map_string_int32.freeze
376      m.map_string_msg.freeze
377
378      assert m.map_string_int32.frozen?
379      assert m.map_string_msg.frozen?
380
381      assert_raise(FrozenErrorType) { m.map_string_int32['foo'] = 1 }
382      assert_raise(FrozenErrorType) { m.map_string_msg['bar'] = proto_module::TestMessage2.new }
383      assert_raise(FrozenErrorType) { m.map_string_int32.delete('a') }
384      assert_raise(FrozenErrorType) { m.map_string_int32.clear }
385    end
386  end
387end
388