1# Protocol Buffers - Google's data interchange format 2# Copyright 2008 Google Inc. All rights reserved. 3# 4# Use of this source code is governed by a BSD-style 5# license that can be found in the LICENSE file or at 6# https://developers.google.com/open-source/licenses/bsd 7 8"""Tests for google.protobuf.message_factory.""" 9 10__author__ = 'matthewtoia@google.com (Matt Toia)' 11 12import unittest 13import gc 14 15from google.protobuf import descriptor_pb2 16from google.protobuf.internal import api_implementation 17from google.protobuf.internal import factory_test1_pb2 18from google.protobuf.internal import factory_test2_pb2 19from google.protobuf.internal import testing_refleaks 20from google.protobuf import descriptor_database 21from google.protobuf import descriptor_pool 22from google.protobuf import message_factory 23from google.protobuf import descriptor 24 25@testing_refleaks.TestCase 26class MessageFactoryTest(unittest.TestCase): 27 28 def setUp(self): 29 self.factory_test1_fd = descriptor_pb2.FileDescriptorProto.FromString( 30 factory_test1_pb2.DESCRIPTOR.serialized_pb) 31 self.factory_test2_fd = descriptor_pb2.FileDescriptorProto.FromString( 32 factory_test2_pb2.DESCRIPTOR.serialized_pb) 33 34 def _ExerciseDynamicClass(self, cls): 35 msg = cls() 36 msg.mandatory = 42 37 msg.nested_factory_2_enum = 0 38 msg.nested_factory_2_message.value = 'nested message value' 39 msg.factory_1_message.factory_1_enum = 1 40 msg.factory_1_message.nested_factory_1_enum = 0 41 msg.factory_1_message.nested_factory_1_message.value = ( 42 'nested message value') 43 msg.factory_1_message.scalar_value = 22 44 msg.factory_1_message.list_value.extend([u'one', u'two', u'three']) 45 msg.factory_1_message.list_value.append(u'four') 46 msg.factory_1_enum = 1 47 msg.nested_factory_1_enum = 0 48 msg.nested_factory_1_message.value = 'nested message value' 49 msg.circular_message.mandatory = 1 50 msg.circular_message.circular_message.mandatory = 2 51 msg.circular_message.scalar_value = 'one deep' 52 msg.scalar_value = 'zero deep' 53 msg.list_value.extend([u'four', u'three', u'two']) 54 msg.list_value.append(u'one') 55 msg.grouped.add() 56 msg.grouped[0].part_1 = 'hello' 57 msg.grouped[0].part_2 = 'world' 58 msg.grouped.add(part_1='testing', part_2='123') 59 msg.loop.loop.mandatory = 2 60 msg.loop.loop.loop.loop.mandatory = 4 61 serialized = msg.SerializeToString() 62 converted = factory_test2_pb2.Factory2Message.FromString(serialized) 63 reserialized = converted.SerializeToString() 64 self.assertEqual(serialized, reserialized) 65 result = cls.FromString(reserialized) 66 self.assertEqual(msg, result) 67 68 def testGetMessageClass(self): 69 db = descriptor_database.DescriptorDatabase() 70 pool = descriptor_pool.DescriptorPool(db) 71 db.Add(self.factory_test1_fd) 72 db.Add(self.factory_test2_fd) 73 cls = message_factory.GetMessageClass(pool.FindMessageTypeByName( 74 'google.protobuf.python.internal.Factory2Message')) 75 self.assertFalse(cls is factory_test2_pb2.Factory2Message) 76 self._ExerciseDynamicClass(cls) 77 cls2 = message_factory.GetMessageClass(pool.FindMessageTypeByName( 78 'google.protobuf.python.internal.Factory2Message')) 79 self.assertTrue(cls is cls2) 80 81 def testGetExistingPrototype(self): 82 # Get Existing Prototype should not create a new class. 83 cls = message_factory.GetMessageClass( 84 descriptor=factory_test2_pb2.Factory2Message.DESCRIPTOR) 85 msg = factory_test2_pb2.Factory2Message() 86 self.assertIsInstance(msg, cls) 87 self.assertIsInstance(msg.factory_1_message, 88 factory_test1_pb2.Factory1Message) 89 90 def testGetMessages(self): 91 # performed twice because multiple calls with the same input must be allowed 92 for _ in range(2): 93 # GetMessage should work regardless of the order the FileDescriptorProto 94 # are provided. In particular, the function should succeed when the files 95 # are not in the topological order of dependencies. 96 97 # Assuming factory_test2_fd depends on factory_test1_fd. 98 self.assertIn(self.factory_test1_fd.name, 99 self.factory_test2_fd.dependency) 100 # Get messages should work when a file comes before its dependencies: 101 # factory_test2_fd comes before factory_test1_fd. 102 messages = message_factory.GetMessages([self.factory_test2_fd, 103 self.factory_test1_fd], 104 descriptor_pool.Default()) 105 self.assertTrue( 106 set(['google.protobuf.python.internal.Factory2Message', 107 'google.protobuf.python.internal.Factory1Message'], 108 ).issubset(set(messages.keys()))) 109 self._ExerciseDynamicClass( 110 messages['google.protobuf.python.internal.Factory2Message']) 111 factory_msg1 = messages['google.protobuf.python.internal.Factory1Message'] 112 self.assertTrue(set( 113 ['google.protobuf.python.internal.Factory2Message.one_more_field', 114 'google.protobuf.python.internal.another_field'],).issubset(set( 115 ext.full_name 116 for ext in factory_msg1.DESCRIPTOR.file.pool.FindAllExtensions( 117 factory_msg1.DESCRIPTOR)))) 118 msg1 = messages['google.protobuf.python.internal.Factory1Message']() 119 ext1 = msg1.Extensions._FindExtensionByName( 120 'google.protobuf.python.internal.Factory2Message.one_more_field') 121 ext2 = msg1.Extensions._FindExtensionByName( 122 'google.protobuf.python.internal.another_field') 123 self.assertEqual(0, len(msg1.Extensions)) 124 msg1.Extensions[ext1] = 'test1' 125 msg1.Extensions[ext2] = 'test2' 126 self.assertEqual('test1', msg1.Extensions[ext1]) 127 self.assertEqual('test2', msg1.Extensions[ext2]) 128 self.assertEqual(None, 129 msg1.Extensions._FindExtensionByNumber(12321)) 130 self.assertEqual(2, len(msg1.Extensions)) 131 if api_implementation.Type() == 'python': 132 self.assertEqual(None, 133 msg1.Extensions._FindExtensionByName(0)) 134 self.assertEqual(None, 135 msg1.Extensions._FindExtensionByNumber('')) 136 else: 137 self.assertRaises(TypeError, msg1.Extensions._FindExtensionByName, 0) 138 self.assertRaises(TypeError, msg1.Extensions._FindExtensionByNumber, '') 139 140 def testDuplicateExtensionNumber(self): 141 pool = descriptor_pool.DescriptorPool() 142 143 # Add Container message. 144 f = descriptor_pb2.FileDescriptorProto( 145 name='google/protobuf/internal/container.proto', 146 package='google.protobuf.python.internal') 147 f.message_type.add(name='Container').extension_range.add(start=1, end=10) 148 pool.Add(f) 149 msgs = message_factory.GetMessageClassesForFiles([f.name], pool) 150 self.assertIn('google.protobuf.python.internal.Container', msgs) 151 152 # Extend container. 153 f = descriptor_pb2.FileDescriptorProto( 154 name='google/protobuf/internal/extension.proto', 155 package='google.protobuf.python.internal', 156 dependency=['google/protobuf/internal/container.proto']) 157 msg = f.message_type.add(name='Extension') 158 msg.extension.add( 159 name='extension_field', 160 number=2, 161 label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, 162 type_name='Extension', 163 extendee='Container', 164 ) 165 pool.Add(f) 166 msgs = message_factory.GetMessageClassesForFiles([f.name], pool) 167 self.assertIn('google.protobuf.python.internal.Extension', msgs) 168 169 # Add Duplicate extending the same field number. 170 f = descriptor_pb2.FileDescriptorProto( 171 name='google/protobuf/internal/duplicate.proto', 172 package='google.protobuf.python.internal', 173 dependency=['google/protobuf/internal/container.proto']) 174 msg = f.message_type.add(name='Duplicate') 175 msg.extension.add( 176 name='extension_field', 177 number=2, 178 label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, 179 type_name='Duplicate', 180 extendee='Container', 181 ) 182 pool.Add(f) 183 184 with self.assertRaises(Exception) as cm: 185 message_factory.GetMessageClassesForFiles([f.name], pool) 186 187 self.assertIn(str(cm.exception), 188 ['Extensions ' 189 '"google.protobuf.python.internal.Duplicate.extension_field" and' 190 ' "google.protobuf.python.internal.Extension.extension_field"' 191 ' both try to extend message type' 192 ' "google.protobuf.python.internal.Container"' 193 ' with field number 2.', 194 'Double registration of Extensions']) 195 196 def testExtensionValueInDifferentFile(self): 197 # Add Container message. 198 f1 = descriptor_pb2.FileDescriptorProto( 199 name='google/protobuf/internal/container.proto', 200 package='google.protobuf.python.internal') 201 f1.message_type.add(name='Container').extension_range.add(start=1, end=10) 202 203 # Add ValueType message. 204 f2 = descriptor_pb2.FileDescriptorProto( 205 name='google/protobuf/internal/value_type.proto', 206 package='google.protobuf.python.internal') 207 f2.message_type.add(name='ValueType').field.add( 208 name='setting', 209 number=1, 210 label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, 211 type=descriptor_pb2.FieldDescriptorProto.TYPE_INT32, 212 default_value='123') 213 214 # Extend container with field of ValueType. 215 f3 = descriptor_pb2.FileDescriptorProto( 216 name='google/protobuf/internal/extension.proto', 217 package='google.protobuf.python.internal', 218 dependency=[f1.name, f2.name]) 219 f3.extension.add( 220 name='top_level_extension_field', 221 number=2, 222 label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, 223 type_name='ValueType', 224 extendee='Container', 225 ) 226 f3.message_type.add(name='Extension').extension.add( 227 name='nested_extension_field', 228 number=3, 229 label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, 230 type_name='ValueType', 231 extendee='Container', 232 ) 233 234 pool = descriptor_pool.Default() 235 try: 236 pool.Add(f1) 237 pool.Add(f2) 238 pool.Add(f3) 239 except: 240 pass 241 msgs = message_factory.GetMessageClassesForFiles( 242 [f1.name, f3.name], pool) # Deliberately not f2. 243 msg = msgs['google.protobuf.python.internal.Container'] 244 desc = msgs['google.protobuf.python.internal.Extension'].DESCRIPTOR 245 ext1 = desc.file.extensions_by_name['top_level_extension_field'] 246 ext2 = desc.extensions_by_name['nested_extension_field'] 247 m = msg() 248 m.Extensions[ext1].setting = 234 249 m.Extensions[ext2].setting = 345 250 serialized = m.SerializeToString() 251 252 f1.name='google/protobuf/internal/another/container.proto' 253 f1.package='google.protobuf.python.internal.another' 254 f2.name='google/protobuf/internal/another/value_type.proto' 255 f2.package='google.protobuf.python.internal.another' 256 f3.name='google/protobuf/internal/another/extension.proto' 257 f3.package='google.protobuf.python.internal.another' 258 f3.ClearField('dependency') 259 f3.dependency.extend([f1.name, f2.name]) 260 try: 261 pool.Add(f1) 262 pool.Add(f2) 263 pool.Add(f3) 264 except: 265 pass 266 msgs = message_factory.GetMessageClassesForFiles( 267 [f1.name, f3.name], pool) # Deliberately not f2. 268 msg = msgs['google.protobuf.python.internal.another.Container'] 269 desc = msgs['google.protobuf.python.internal.another.Extension'].DESCRIPTOR 270 ext1 = desc.file.extensions_by_name['top_level_extension_field'] 271 ext2 = desc.extensions_by_name['nested_extension_field'] 272 m = msg.FromString(serialized) 273 self.assertEqual(2, len(m.ListFields())) 274 self.assertEqual(234, m.Extensions[ext1].setting) 275 self.assertEqual(345, m.Extensions[ext2].setting) 276 277 def testDescriptorKeepConcreteClass(self): 278 def loadFile(): 279 f= descriptor_pb2.FileDescriptorProto( 280 name='google/protobuf/internal/meta_class.proto', 281 package='google.protobuf.python.internal') 282 msg_proto = f.message_type.add(name='Empty') 283 msg_proto.nested_type.add(name='Nested') 284 msg_proto.field.add(name='nested_field', 285 number=1, 286 label=descriptor.FieldDescriptor.LABEL_REPEATED, 287 type=descriptor.FieldDescriptor.TYPE_MESSAGE, 288 type_name='Nested') 289 return message_factory.GetMessages([f]) 290 291 messages = loadFile() 292 for des, meta_class in messages.items(): 293 message = meta_class() 294 nested_des = message.DESCRIPTOR.nested_types_by_name['Nested'] 295 nested_msg = nested_des._concrete_class() 296 297 def testOndemandCreateMetaClass(self): 298 def loadFile(): 299 f = descriptor_pb2.FileDescriptorProto.FromString( 300 factory_test1_pb2.DESCRIPTOR.serialized_pb) 301 return message_factory.GetMessages([f]) 302 303 messages = loadFile() 304 data = factory_test1_pb2.Factory1Message() 305 data.map_field['hello'] = 'welcome' 306 # Force GC to collect. UPB python will clean up the map entry class. 307 # cpp extension and pure python will still keep the map entry class. 308 gc.collect() 309 message = messages['google.protobuf.python.internal.Factory1Message']() 310 message.ParseFromString(data.SerializeToString()) 311 value = message.map_field 312 values = [ 313 # The entry class will be created on demand in upb python. 314 value.GetEntryClass()(key=k, value=value[k]) for k in sorted(value) 315 ] 316 gc.collect() 317 self.assertEqual(1, len(values)) 318 self.assertEqual('hello', values[0].key) 319 self.assertEqual('welcome', values[0].value) 320 321if __name__ == '__main__': 322 unittest.main() 323