diff options
Diffstat (limited to 'python/google/protobuf/internal')
-rw-r--r-- | python/google/protobuf/internal/any_test.proto | 14 | ||||
-rwxr-xr-x | python/google/protobuf/internal/containers.py | 21 | ||||
-rw-r--r-- | python/google/protobuf/internal/descriptor_pool_test.py | 5 | ||||
-rwxr-xr-x | python/google/protobuf/internal/descriptor_test.py | 19 | ||||
-rwxr-xr-x | python/google/protobuf/internal/generator_test.py | 3 | ||||
-rw-r--r-- | python/google/protobuf/internal/json_format_test.py | 78 | ||||
-rwxr-xr-x | python/google/protobuf/internal/message_test.py | 45 | ||||
-rwxr-xr-x | python/google/protobuf/internal/python_message.py | 6 | ||||
-rwxr-xr-x | python/google/protobuf/internal/reflection_test.py | 41 | ||||
-rw-r--r-- | python/google/protobuf/internal/testing_refleaks.py | 124 | ||||
-rwxr-xr-x | python/google/protobuf/internal/text_format_test.py | 25 | ||||
-rwxr-xr-x | python/google/protobuf/internal/unknown_fields_test.py | 107 | ||||
-rw-r--r-- | python/google/protobuf/internal/well_known_types.py | 75 | ||||
-rw-r--r-- | python/google/protobuf/internal/well_known_types_test.py | 132 |
14 files changed, 570 insertions, 125 deletions
diff --git a/python/google/protobuf/internal/any_test.proto b/python/google/protobuf/internal/any_test.proto index cd641ca0..76a7ebd6 100644 --- a/python/google/protobuf/internal/any_test.proto +++ b/python/google/protobuf/internal/any_test.proto @@ -30,13 +30,21 @@ // Author: jieluo@google.com (Jie Luo) -syntax = "proto3"; +syntax = "proto2"; package google.protobuf.internal; import "google/protobuf/any.proto"; message TestAny { - google.protobuf.Any value = 1; - int32 int_value = 2; + optional google.protobuf.Any value = 1; + optional int32 int_value = 2; + extensions 10 to max; +} + +message TestAnyExtension1 { + extend TestAny { + optional TestAnyExtension1 extension1 = 98418603; + } + optional int32 i = 15; } diff --git a/python/google/protobuf/internal/containers.py b/python/google/protobuf/internal/containers.py index ce46d08c..de13018e 100755 --- a/python/google/protobuf/internal/containers.py +++ b/python/google/protobuf/internal/containers.py @@ -436,9 +436,11 @@ class ScalarMap(MutableMapping): """Simple, type-checked, dict-like container for holding repeated scalars.""" # Disallows assignment to other attributes. - __slots__ = ['_key_checker', '_value_checker', '_values', '_message_listener'] + __slots__ = ['_key_checker', '_value_checker', '_values', '_message_listener', + '_entry_descriptor'] - def __init__(self, message_listener, key_checker, value_checker): + def __init__(self, message_listener, key_checker, value_checker, + entry_descriptor): """ Args: message_listener: A MessageListener implementation. @@ -448,10 +450,12 @@ class ScalarMap(MutableMapping): inserted into this container. value_checker: A type_checkers.ValueChecker instance to run on values inserted into this container. + entry_descriptor: The MessageDescriptor of a map entry: key and value. """ self._message_listener = message_listener self._key_checker = key_checker self._value_checker = value_checker + self._entry_descriptor = entry_descriptor self._values = {} def __getitem__(self, key): @@ -513,6 +517,9 @@ class ScalarMap(MutableMapping): self._values.clear() self._message_listener.Modified() + def GetEntryClass(self): + return self._entry_descriptor._concrete_class + class MessageMap(MutableMapping): @@ -520,9 +527,10 @@ class MessageMap(MutableMapping): # Disallows assignment to other attributes. __slots__ = ['_key_checker', '_values', '_message_listener', - '_message_descriptor'] + '_message_descriptor', '_entry_descriptor'] - def __init__(self, message_listener, message_descriptor, key_checker): + def __init__(self, message_listener, message_descriptor, key_checker, + entry_descriptor): """ Args: message_listener: A MessageListener implementation. @@ -532,10 +540,12 @@ class MessageMap(MutableMapping): inserted into this container. value_checker: A type_checkers.ValueChecker instance to run on values inserted into this container. + entry_descriptor: The MessageDescriptor of a map entry: key and value. """ self._message_listener = message_listener self._message_descriptor = message_descriptor self._key_checker = key_checker + self._entry_descriptor = entry_descriptor self._values = {} def __getitem__(self, key): @@ -613,3 +623,6 @@ class MessageMap(MutableMapping): def clear(self): self._values.clear() self._message_listener.Modified() + + def GetEntryClass(self): + return self._entry_descriptor._concrete_class diff --git a/python/google/protobuf/internal/descriptor_pool_test.py b/python/google/protobuf/internal/descriptor_pool_test.py index 3c8c7935..d4de2d81 100644 --- a/python/google/protobuf/internal/descriptor_pool_test.py +++ b/python/google/protobuf/internal/descriptor_pool_test.py @@ -119,6 +119,7 @@ class DescriptorPoolTest(unittest.TestCase): self.assertEqual('google.protobuf.python.internal.Factory1Message', msg1.full_name) self.assertEqual(None, msg1.containing_type) + self.assertFalse(msg1.has_options) nested_msg1 = msg1.nested_types[0] self.assertEqual('NestedFactory1Message', nested_msg1.name) @@ -202,6 +203,7 @@ class DescriptorPoolTest(unittest.TestCase): self.assertIsInstance(enum1, descriptor.EnumDescriptor) self.assertEqual(0, enum1.values_by_name['FACTORY_1_VALUE_0'].number) self.assertEqual(1, enum1.values_by_name['FACTORY_1_VALUE_1'].number) + self.assertFalse(enum1.has_options) nested_enum1 = self.pool.FindEnumTypeByName( 'google.protobuf.python.internal.Factory1Message.NestedFactory1Enum') @@ -234,6 +236,8 @@ class DescriptorPoolTest(unittest.TestCase): 'google.protobuf.python.internal.Factory1Message.list_value') self.assertEqual(field.name, 'list_value') self.assertEqual(field.label, field.LABEL_REPEATED) + self.assertFalse(field.has_options) + with self.assertRaises(KeyError): self.pool.FindFieldByName('Does not exist') @@ -448,6 +452,7 @@ class EnumField(object): test.assertTrue(field_desc.has_default_value) test.assertEqual(enum_desc.values_by_name[self.default_value].number, field_desc.default_value) + test.assertFalse(enum_desc.values_by_name[self.default_value].has_options) test.assertEqual(msg_desc, field_desc.containing_type) test.assertEqual(enum_desc, field_desc.enum_type) diff --git a/python/google/protobuf/internal/descriptor_test.py b/python/google/protobuf/internal/descriptor_test.py index 623198c8..1f148ab9 100755 --- a/python/google/protobuf/internal/descriptor_test.py +++ b/python/google/protobuf/internal/descriptor_test.py @@ -766,6 +766,8 @@ class MakeDescriptorTest(unittest.TestCase): 'Foo2.Sub.bar_field') self.assertEqual(result.nested_types[0].fields[0].enum_type, result.nested_types[0].enum_types[0]) + self.assertFalse(result.has_options) + self.assertFalse(result.fields[0].has_options) def testMakeDescriptorWithUnsignedIntField(self): file_descriptor_proto = descriptor_pb2.FileDescriptorProto() @@ -818,6 +820,23 @@ class MakeDescriptorTest(unittest.TestCase): self.assertEqual(result.fields[index].camelcase_name, camelcase_names[index]) + def testJsonName(self): + descriptor_proto = descriptor_pb2.DescriptorProto() + descriptor_proto.name = 'TestJsonName' + names = ['field_name', 'fieldName', 'FieldName', + '_field_name', 'FIELD_NAME', 'json_name'] + json_names = ['fieldName', 'fieldName', 'FieldName', + 'FieldName', 'FIELDNAME', '@type'] + for index in range(len(names)): + field = descriptor_proto.field.add() + field.number = index + 1 + field.name = names[index] + field.json_name = '@type' + result = descriptor.MakeDescriptor(descriptor_proto) + for index in range(len(json_names)): + self.assertEqual(result.fields[index].json_name, + json_names[index]) + if __name__ == '__main__': unittest.main() diff --git a/python/google/protobuf/internal/generator_test.py b/python/google/protobuf/internal/generator_test.py index 83ea5f50..7f13f9da 100755 --- a/python/google/protobuf/internal/generator_test.py +++ b/python/google/protobuf/internal/generator_test.py @@ -227,7 +227,8 @@ class GeneratorTest(unittest.TestCase): [unittest_import_pb2.DESCRIPTOR]) self.assertEqual(unittest_import_pb2.DESCRIPTOR.dependencies, [unittest_import_public_pb2.DESCRIPTOR]) - + self.assertEqual(unittest_import_pb2.DESCRIPTOR.public_dependencies, + [unittest_import_public_pb2.DESCRIPTOR]) def testNoGenericServices(self): self.assertTrue(hasattr(unittest_no_generic_services_pb2, "TestMessage")) self.assertTrue(hasattr(unittest_no_generic_services_pb2, "FOO")) diff --git a/python/google/protobuf/internal/json_format_test.py b/python/google/protobuf/internal/json_format_test.py index a5ee8ace..5ed65622 100644 --- a/python/google/protobuf/internal/json_format_test.py +++ b/python/google/protobuf/internal/json_format_test.py @@ -205,6 +205,15 @@ class JsonFormatTest(JsonFormatBase): parsed_message = json_format_proto3_pb2.TestMessage() self.CheckParseBack(message, parsed_message) + def testIntegersRepresentedAsFloat(self): + message = json_format_proto3_pb2.TestMessage() + json_format.Parse('{"int32Value": -2.147483648e9}', message) + self.assertEqual(message.int32_value, -2147483648) + json_format.Parse('{"int32Value": 1e5}', message) + self.assertEqual(message.int32_value, 100000) + json_format.Parse('{"int32Value": 1.0}', message) + self.assertEqual(message.int32_value, 1) + def testMapFields(self): message = json_format_proto3_pb2.TestMap() message.bool_map[True] = 1 @@ -428,6 +437,9 @@ class JsonFormatTest(JsonFormatBase): ' "value": "hello",' ' "repeatedValue": [11.1, false, null, null]' '}')) + message.Clear() + json_format.Parse('{"value": null}', message) + self.assertEqual(message.value.WhichOneof('kind'), 'null_value') def testListValueMessage(self): message = json_format_proto3_pb2.TestListValue() @@ -600,6 +612,11 @@ class JsonFormatTest(JsonFormatBase): '}', parsed_message) self.assertEqual(message, parsed_message) + # Null and {} should have different behavior for sub message. + self.assertFalse(parsed_message.HasField('message_value')) + json_format.Parse('{"messageValue": {}}', parsed_message) + self.assertTrue(parsed_message.HasField('message_value')) + # Null is not allowed to be used as an element in repeated field. self.assertRaisesRegexp( json_format.ParseError, 'Failed to parse repeatedInt32Value field: ' @@ -621,15 +638,16 @@ class JsonFormatTest(JsonFormatBase): self.CheckError('', r'Failed to load JSON: (Expecting value)|(No JSON).') - def testParseBadEnumValue(self): - self.CheckError( - '{"enumValue": 1}', - 'Enum value must be a string literal with double quotes. ' - 'Type "proto3.EnumType" has no value named 1.') + def testParseEnumValue(self): + message = json_format_proto3_pb2.TestMessage() + text = '{"enumValue": 0}' + json_format.Parse(text, message) + text = '{"enumValue": 1}' + json_format.Parse(text, message) self.CheckError( '{"enumValue": "baz"}', - 'Enum value must be a string literal with double quotes. ' - 'Type "proto3.EnumType" has no value named baz.') + 'Failed to parse enumValue field: Invalid enum value baz ' + 'for enum type proto3.EnumType.') def testParseBadIdentifer(self): self.CheckError('{int32Value: 1}', @@ -672,12 +690,12 @@ class JsonFormatTest(JsonFormatBase): text = '{"int32Value": 0x12345}' self.assertRaises(json_format.ParseError, json_format.Parse, text, message) + self.CheckError('{"int32Value": 1.5}', + 'Failed to parse int32Value field: ' + 'Couldn\'t parse integer: 1.5.') self.CheckError('{"int32Value": 012345}', (r'Failed to load JSON: Expecting \'?,\'? delimiter: ' r'line 1.')) - self.CheckError('{"int32Value": 1.0}', - 'Failed to parse int32Value field: ' - 'Couldn\'t parse integer: 1.0.') self.CheckError('{"int32Value": " 1 "}', 'Failed to parse int32Value field: ' 'Couldn\'t parse integer: " 1 ".') @@ -687,9 +705,6 @@ class JsonFormatTest(JsonFormatBase): self.CheckError('{"int32Value": 12345678901234567890}', 'Failed to parse int32Value field: Value out of range: ' '12345678901234567890.') - self.CheckError('{"int32Value": 1e5}', - 'Failed to parse int32Value field: ' - 'Couldn\'t parse integer: 100000.0.') self.CheckError('{"uint32Value": -1}', 'Failed to parse uint32Value field: ' 'Value out of range: -1.') @@ -810,6 +825,43 @@ class JsonFormatTest(JsonFormatBase): r'"value": 1234}') json_format.Parse(text, message) + def testPreservingProtoFieldNames(self): + message = json_format_proto3_pb2.TestMessage() + message.int32_value = 12345 + self.assertEqual('{\n "int32Value": 12345\n}', + json_format.MessageToJson(message)) + self.assertEqual('{\n "int32_value": 12345\n}', + json_format.MessageToJson(message, False, True)) + + # Parsers accept both original proto field names and lowerCamelCase names. + message = json_format_proto3_pb2.TestMessage() + json_format.Parse('{"int32Value": 54321}', message) + self.assertEqual(54321, message.int32_value) + json_format.Parse('{"int32_value": 12345}', message) + self.assertEqual(12345, message.int32_value) + + def testParseDict(self): + expected = 12345 + js_dict = {'int32Value': expected} + message = json_format_proto3_pb2.TestMessage() + json_format.ParseDict(js_dict, message) + self.assertEqual(expected, message.int32_value) + + def testMessageToDict(self): + message = json_format_proto3_pb2.TestMessage() + message.int32_value = 12345 + expected = {'int32Value': 12345} + self.assertEqual(expected, + json_format.MessageToDict(message)) + + def testJsonName(self): + message = json_format_proto3_pb2.TestCustomJsonName() + message.value = 12345 + self.assertEqual('{\n "@value": 12345\n}', + json_format.MessageToJson(message)) + parsed_message = json_format_proto3_pb2.TestCustomJsonName() + self.CheckParseBack(message, parsed_message) + if __name__ == '__main__': unittest.main() diff --git a/python/google/protobuf/internal/message_test.py b/python/google/protobuf/internal/message_test.py index 1e95adf9..9986c0d9 100755 --- a/python/google/protobuf/internal/message_test.py +++ b/python/google/protobuf/internal/message_test.py @@ -67,6 +67,7 @@ from google.protobuf import text_format from google.protobuf.internal import api_implementation from google.protobuf.internal import packed_field_test_pb2 from google.protobuf.internal import test_util +from google.protobuf.internal import testing_refleaks from google.protobuf import message from google.protobuf.internal import _parameterized @@ -88,10 +89,13 @@ def IsNegInf(val): return isinf(val) and (val < 0) -@_parameterized.Parameters( - (unittest_pb2), - (unittest_proto3_arena_pb2)) -class MessageTest(unittest.TestCase): +BaseTestCase = testing_refleaks.BaseTestCase + + +@_parameterized.NamedParameters( + ('_proto2', unittest_pb2), + ('_proto3', unittest_proto3_arena_pb2)) +class MessageTest(BaseTestCase): def testBadUtf8String(self, message_module): if api_implementation.Type() != 'python': @@ -957,7 +961,7 @@ class MessageTest(unittest.TestCase): # Class to test proto2-only features (required, extensions, etc.) -class Proto2Test(unittest.TestCase): +class Proto2Test(BaseTestCase): def testFieldPresence(self): message = unittest_pb2.TestAllTypes() @@ -1113,6 +1117,7 @@ class Proto2Test(unittest.TestCase): optional_bytes=b'x', optionalgroup={'a': 400}, optional_nested_message={'bb': 500}, + optional_foreign_message={}, optional_nested_enum='BAZ', repeatedgroup=[{'a': 600}, {'a': 700}], @@ -1125,8 +1130,12 @@ class Proto2Test(unittest.TestCase): self.assertEqual(300.5, message.optional_float) self.assertEqual(b'x', message.optional_bytes) self.assertEqual(400, message.optionalgroup.a) - self.assertIsInstance(message.optional_nested_message, unittest_pb2.TestAllTypes.NestedMessage) + self.assertIsInstance(message.optional_nested_message, + unittest_pb2.TestAllTypes.NestedMessage) self.assertEqual(500, message.optional_nested_message.bb) + self.assertTrue(message.HasField('optional_foreign_message')) + self.assertEqual(message.optional_foreign_message, + unittest_pb2.ForeignMessage()) self.assertEqual(unittest_pb2.TestAllTypes.BAZ, message.optional_nested_enum) self.assertEqual(2, len(message.repeatedgroup)) @@ -1164,7 +1173,7 @@ class Proto2Test(unittest.TestCase): # Class to test proto3-only features/behavior (updated field presence & enums) -class Proto3Test(unittest.TestCase): +class Proto3Test(BaseTestCase): # Utility method for comparing equality with a map. def assertMapIterEquals(self, map_iter, dict_value): @@ -1720,7 +1729,7 @@ class Proto3Test(unittest.TestCase): -class ValidTypeNamesTest(unittest.TestCase): +class ValidTypeNamesTest(BaseTestCase): def assertImportFromName(self, msg, base_name): # Parse <type 'module.class_name'> to extra 'some.name' as a string. @@ -1741,7 +1750,7 @@ class ValidTypeNamesTest(unittest.TestCase): self.assertImportFromName(pb.repeated_int32, 'Scalar') self.assertImportFromName(pb.repeated_nested_message, 'Composite') -class PackedFieldTest(unittest.TestCase): +class PackedFieldTest(BaseTestCase): def setMessage(self, message): message.repeated_int32.append(1) @@ -1800,10 +1809,14 @@ class PackedFieldTest(unittest.TestCase): @unittest.skipIf(api_implementation.Type() != 'cpp', 'explicit tests of the C++ implementation') -class OversizeProtosTest(unittest.TestCase): - - def setUp(self): - self.file_desc = """ +class OversizeProtosTest(BaseTestCase): + + @classmethod + def setUpClass(cls): + # At the moment, reference cycles between DescriptorPool and Message classes + # are not detected and these objects are never freed. + # To avoid errors with ReferenceLeakChecker, we create the class only once. + file_desc = """ name: "f/f.msg2" package: "f" message_type { @@ -1828,10 +1841,12 @@ class OversizeProtosTest(unittest.TestCase): """ pool = descriptor_pool.DescriptorPool() desc = descriptor_pb2.FileDescriptorProto() - text_format.Parse(self.file_desc, desc) + text_format.Parse(file_desc, desc) pool.Add(desc) - self.proto_cls = message_factory.MessageFactory(pool).GetPrototype( + cls.proto_cls = message_factory.MessageFactory(pool).GetPrototype( pool.FindMessageTypeByName('f.msg2')) + + def setUp(self): self.p = self.proto_cls() self.p.field.payload = 'c' * (1024 * 1024 * 64 + 1) self.p_serialized = self.p.SerializeToString() diff --git a/python/google/protobuf/internal/python_message.py b/python/google/protobuf/internal/python_message.py index c0d0ad45..60b4baad 100755 --- a/python/google/protobuf/internal/python_message.py +++ b/python/google/protobuf/internal/python_message.py @@ -380,13 +380,15 @@ def _GetInitializeDefaultForMap(field): if _IsMessageMapField(field): def MakeMessageMapDefault(message): return containers.MessageMap( - message._listener_for_children, value_field.message_type, key_checker) + message._listener_for_children, value_field.message_type, key_checker, + field.message_type) return MakeMessageMapDefault else: value_checker = type_checkers.GetTypeChecker(value_field) def MakePrimitiveMapDefault(message): return containers.ScalarMap( - message._listener_for_children, key_checker, value_checker) + message._listener_for_children, key_checker, value_checker, + field.message_type) return MakePrimitiveMapDefault def _DefaultValueConstructorForField(field): diff --git a/python/google/protobuf/internal/reflection_test.py b/python/google/protobuf/internal/reflection_test.py index 6f3b818a..dad79c37 100755 --- a/python/google/protobuf/internal/reflection_test.py +++ b/python/google/protobuf/internal/reflection_test.py @@ -60,9 +60,13 @@ from google.protobuf.internal import more_messages_pb2 from google.protobuf.internal import message_set_extensions_pb2 from google.protobuf.internal import wire_format from google.protobuf.internal import test_util +from google.protobuf.internal import testing_refleaks from google.protobuf.internal import decoder +BaseTestCase = testing_refleaks.BaseTestCase + + class _MiniDecoder(object): """Decodes a stream of values from a string. @@ -108,7 +112,7 @@ class _MiniDecoder(object): return self._pos == len(self._bytes) -class ReflectionTest(unittest.TestCase): +class ReflectionTest(BaseTestCase): def assertListsEqual(self, values, others): self.assertEqual(len(values), len(others)) @@ -1552,6 +1556,20 @@ class ReflectionTest(unittest.TestCase): self.assertFalse(proto.HasField('optional_foreign_message')) self.assertEqual(0, proto.optional_foreign_message.c) + def testDisconnectingInOneof(self): + m = unittest_pb2.TestOneof2() # This message has two messages in a oneof. + m.foo_message.qux_int = 5 + sub_message = m.foo_message + # Accessing another message's field does not clear the first one + self.assertEqual(m.foo_lazy_message.qux_int, 0) + self.assertEqual(m.foo_message.qux_int, 5) + # But mutating another message in the oneof detaches the first one. + m.foo_lazy_message.qux_int = 6 + self.assertEqual(m.foo_message.qux_int, 0) + # The reference we got above was detached and is still valid. + self.assertEqual(sub_message.qux_int, 5) + sub_message.qux_int = 7 + def testOneOf(self): proto = unittest_pb2.TestAllTypes() proto.oneof_uint32 = 10 @@ -1810,7 +1828,7 @@ class ReflectionTest(unittest.TestCase): # into separate TestCase classes. -class TestAllTypesEqualityTest(unittest.TestCase): +class TestAllTypesEqualityTest(BaseTestCase): def setUp(self): self.first_proto = unittest_pb2.TestAllTypes() @@ -1826,7 +1844,7 @@ class TestAllTypesEqualityTest(unittest.TestCase): self.assertEqual(self.first_proto, self.second_proto) -class FullProtosEqualityTest(unittest.TestCase): +class FullProtosEqualityTest(BaseTestCase): """Equality tests using completely-full protos as a starting point.""" @@ -1912,7 +1930,7 @@ class FullProtosEqualityTest(unittest.TestCase): self.assertEqual(self.first_proto, self.second_proto) -class ExtensionEqualityTest(unittest.TestCase): +class ExtensionEqualityTest(BaseTestCase): def testExtensionEquality(self): first_proto = unittest_pb2.TestAllExtensions() @@ -1945,7 +1963,7 @@ class ExtensionEqualityTest(unittest.TestCase): self.assertEqual(first_proto, second_proto) -class MutualRecursionEqualityTest(unittest.TestCase): +class MutualRecursionEqualityTest(BaseTestCase): def testEqualityWithMutualRecursion(self): first_proto = unittest_pb2.TestMutualRecursionA() @@ -1957,7 +1975,7 @@ class MutualRecursionEqualityTest(unittest.TestCase): self.assertEqual(first_proto, second_proto) -class ByteSizeTest(unittest.TestCase): +class ByteSizeTest(BaseTestCase): def setUp(self): self.proto = unittest_pb2.TestAllTypes() @@ -2253,7 +2271,7 @@ class ByteSizeTest(unittest.TestCase): # * Handling of empty submessages (with and without "has" # bits set). -class SerializationTest(unittest.TestCase): +class SerializationTest(BaseTestCase): def testSerializeEmtpyMessage(self): first_proto = unittest_pb2.TestAllTypes() @@ -2814,7 +2832,7 @@ class SerializationTest(unittest.TestCase): self.assertEqual(3, proto.repeated_int32[2]) -class OptionsTest(unittest.TestCase): +class OptionsTest(BaseTestCase): def testMessageOptions(self): proto = message_set_extensions_pb2.TestMessageSet() @@ -2841,7 +2859,7 @@ class OptionsTest(unittest.TestCase): -class ClassAPITest(unittest.TestCase): +class ClassAPITest(BaseTestCase): @unittest.skipIf( api_implementation.Type() == 'cpp' and api_implementation.Version() == 2, @@ -2924,6 +2942,9 @@ class ClassAPITest(unittest.TestCase): text_format.Merge(file_descriptor_str, file_descriptor) return file_descriptor.SerializeToString() + @testing_refleaks.SkipReferenceLeakChecker('MakeDescriptor is not repeatable') + # This test can only run once; the second time, it raises errors about + # conflicting message descriptors. def testParsingFlatClassWithExplicitClassDeclaration(self): """Test that the generated class can parse a flat message.""" # TODO(xiaofeng): This test fails with cpp implemetnation in the call @@ -2948,6 +2969,7 @@ class ClassAPITest(unittest.TestCase): text_format.Merge(msg_str, msg) self.assertEqual(msg.flat, [0, 1, 2]) + @testing_refleaks.SkipReferenceLeakChecker('MakeDescriptor is not repeatable') def testParsingFlatClass(self): """Test that the generated class can parse a flat message.""" file_descriptor = descriptor_pb2.FileDescriptorProto() @@ -2963,6 +2985,7 @@ class ClassAPITest(unittest.TestCase): text_format.Merge(msg_str, msg) self.assertEqual(msg.flat, [0, 1, 2]) + @testing_refleaks.SkipReferenceLeakChecker('MakeDescriptor is not repeatable') def testParsingNestedClass(self): """Test that the generated class can parse a nested message.""" file_descriptor = descriptor_pb2.FileDescriptorProto() diff --git a/python/google/protobuf/internal/testing_refleaks.py b/python/google/protobuf/internal/testing_refleaks.py new file mode 100644 index 00000000..b2787901 --- /dev/null +++ b/python/google/protobuf/internal/testing_refleaks.py @@ -0,0 +1,124 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# https://developers.google.com/protocol-buffers/ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""A subclass of unittest.TestCase which checks for reference leaks. + +To use: +- Use testing_refleak.BaseTestCase instead of unittest.TestCase +- Configure and compile Python with --with-pydebug + +If sys.gettotalrefcount() is not available (because Python was built without +the Py_DEBUG option), then this module is a no-op and tests will run normally. +""" + +import copy_reg +import gc +import sys + +try: + import unittest2 as unittest #PY26 +except ImportError: + import unittest + + +class LocalTestResult(unittest.TestResult): + """A TestResult which forwards events to a parent object, except for Skips.""" + + def __init__(self, parent_result): + unittest.TestResult.__init__(self) + self.parent_result = parent_result + + def addError(self, test, error): + self.parent_result.addError(test, error) + + def addFailure(self, test, error): + self.parent_result.addFailure(test, error) + + def addSkip(self, test, reason): + pass + + +class ReferenceLeakCheckerTestCase(unittest.TestCase): + """A TestCase which runs tests multiple times, collecting reference counts.""" + + NB_RUNS = 3 + + def run(self, result=None): + # python_message.py registers all Message classes to some pickle global + # registry, which makes the classes immortal. + # We save a copy of this registry, and reset it before we could references. + self._saved_pickle_registry = copy_reg.dispatch_table.copy() + + # Run the test twice, to warm up the instance attributes. + super(ReferenceLeakCheckerTestCase, self).run(result=result) + super(ReferenceLeakCheckerTestCase, self).run(result=result) + + oldrefcount = 0 + local_result = LocalTestResult(result) + + refcount_deltas = [] + for _ in range(self.NB_RUNS): + oldrefcount = self._getRefcounts() + super(ReferenceLeakCheckerTestCase, self).run(result=local_result) + newrefcount = self._getRefcounts() + refcount_deltas.append(newrefcount - oldrefcount) + print refcount_deltas, self + + try: + self.assertEqual(refcount_deltas, [0] * self.NB_RUNS) + except Exception: # pylint: disable=broad-except + result.addError(self, sys.exc_info()) + + def _getRefcounts(self): + copy_reg.dispatch_table.clear() + copy_reg.dispatch_table.update(self._saved_pickle_registry) + # It is sometimes necessary to gc.collect() multiple times, to ensure + # that all objects can be collected. + gc.collect() + gc.collect() + gc.collect() + return sys.gettotalrefcount() + + +if hasattr(sys, 'gettotalrefcount'): + BaseTestCase = ReferenceLeakCheckerTestCase + SkipReferenceLeakChecker = unittest.skip + +else: + # When PyDEBUG is not enabled, run the tests normally. + BaseTestCase = unittest.TestCase + + def SkipReferenceLeakChecker(reason): + del reason # Don't skip, so don't need a reason. + def Same(func): + return func + return Same + + diff --git a/python/google/protobuf/internal/text_format_test.py b/python/google/protobuf/internal/text_format_test.py index 0e38e0e9..ab481ab4 100755 --- a/python/google/protobuf/internal/text_format_test.py +++ b/python/google/protobuf/internal/text_format_test.py @@ -52,6 +52,7 @@ from google.protobuf import unittest_mset_pb2 from google.protobuf import unittest_pb2 from google.protobuf import unittest_proto3_arena_pb2 from google.protobuf.internal import api_implementation +from google.protobuf.internal import any_test_pb2 as test_extend_any from google.protobuf.internal import test_util from google.protobuf.internal import message_set_extensions_pb2 from google.protobuf import descriptor_pool @@ -684,6 +685,21 @@ class Proto2Tests(TextFormatBase): self.assertEqual(23, message.message_set.Extensions[ext1].i) self.assertEqual('foo', message.message_set.Extensions[ext2].str) + def testExtensionInsideAnyMessage(self): + message = test_extend_any.TestAny() + text = ('value {\n' + ' [type.googleapis.com/google.protobuf.internal.TestAny] {\n' + ' [google.protobuf.internal.TestAnyExtension1.extension1] {\n' + ' i: 10\n' + ' }\n' + ' }\n' + '}\n') + text_format.Merge(text, message, descriptor_pool=descriptor_pool.Default()) + self.CompareToGoldenText( + text_format.MessageToString( + message, descriptor_pool=descriptor_pool.Default()), + text) + def testParseMessageByFieldNumber(self): message = unittest_pb2.TestAllTypes() text = ('34: 1\n' 'repeated_uint64: 2\n') @@ -1184,7 +1200,8 @@ class TokenizerTest(unittest.TestCase): 'ID7 : "aa\\"bb"\n\n\n\n ID8: {A:inf B:-inf C:true D:false}\n' 'ID9: 22 ID10: -111111111111111111 ID11: -22\n' 'ID12: 2222222222222222222 ID13: 1.23456f ID14: 1.2e+2f ' - 'false_bool: 0 true_BOOL:t \n true_bool1: 1 false_BOOL1:f ') + 'false_bool: 0 true_BOOL:t \n true_bool1: 1 false_BOOL1:f ' + 'False_bool: False True_bool: True') tokenizer = text_format.Tokenizer(text.splitlines()) methods = [(tokenizer.ConsumeIdentifier, 'identifier1'), ':', (tokenizer.ConsumeString, 'string1'), @@ -1228,7 +1245,11 @@ class TokenizerTest(unittest.TestCase): (tokenizer.ConsumeIdentifier, 'true_bool1'), ':', (tokenizer.ConsumeBool, True), (tokenizer.ConsumeIdentifier, 'false_BOOL1'), ':', - (tokenizer.ConsumeBool, False)] + (tokenizer.ConsumeBool, False), + (tokenizer.ConsumeIdentifier, 'False_bool'), ':', + (tokenizer.ConsumeBool, False), + (tokenizer.ConsumeIdentifier, 'True_bool'), ':', + (tokenizer.ConsumeBool, True)] i = 0 while not tokenizer.AtEnd(): diff --git a/python/google/protobuf/internal/unknown_fields_test.py b/python/google/protobuf/internal/unknown_fields_test.py index 84073f1c..d614eaa8 100755 --- a/python/google/protobuf/internal/unknown_fields_test.py +++ b/python/google/protobuf/internal/unknown_fields_test.py @@ -47,16 +47,20 @@ from google.protobuf.internal import encoder from google.protobuf.internal import message_set_extensions_pb2 from google.protobuf.internal import missing_enum_values_pb2 from google.protobuf.internal import test_util +from google.protobuf.internal import testing_refleaks from google.protobuf.internal import type_checkers +BaseTestCase = testing_refleaks.BaseTestCase + + def SkipIfCppImplementation(func): return unittest.skipIf( api_implementation.Type() == 'cpp' and api_implementation.Version() == 2, 'C++ implementation does not expose unknown fields to Python')(func) -class UnknownFieldsTest(unittest.TestCase): +class UnknownFieldsTest(BaseTestCase): def setUp(self): self.descriptor = unittest_pb2.TestAllTypes.DESCRIPTOR @@ -140,7 +144,7 @@ class UnknownFieldsTest(unittest.TestCase): b'', message.repeated_nested_message[0].SerializeToString()) -class UnknownFieldsAccessorsTest(unittest.TestCase): +class UnknownFieldsAccessorsTest(BaseTestCase): def setUp(self): self.descriptor = unittest_pb2.TestAllTypes.DESCRIPTOR @@ -149,21 +153,18 @@ class UnknownFieldsAccessorsTest(unittest.TestCase): self.all_fields_data = self.all_fields.SerializeToString() self.empty_message = unittest_pb2.TestEmptyMessage() self.empty_message.ParseFromString(self.all_fields_data) - if api_implementation.Type() != 'cpp': - # _unknown_fields is an implementation detail. - self.unknown_fields = self.empty_message._unknown_fields - # All the tests that use GetField() check an implementation detail of the - # Python implementation, which stores unknown fields as serialized strings. - # These tests are skipped by the C++ implementation: it's enough to check that - # the message is correctly serialized. + # GetUnknownField() checks a detail of the Python implementation, which stores + # unknown fields as serialized strings. It cannot be used by the C++ + # implementation: it's enough to check that the message is correctly + # serialized. - def GetField(self, name): + def GetUnknownField(self, name): field_descriptor = self.descriptor.fields_by_name[name] wire_type = type_checkers.FIELD_TYPE_TO_WIRE_TYPE[field_descriptor.type] field_tag = encoder.TagBytes(field_descriptor.number, wire_type) result_dict = {} - for tag_bytes, value in self.unknown_fields: + for tag_bytes, value in self.empty_message._unknown_fields: if tag_bytes == field_tag: decoder = unittest_pb2.TestAllTypes._decoders_by_tag[tag_bytes][0] decoder(value, 0, len(value), self.all_fields, result_dict) @@ -171,37 +172,37 @@ class UnknownFieldsAccessorsTest(unittest.TestCase): @SkipIfCppImplementation def testEnum(self): - value = self.GetField('optional_nested_enum') + value = self.GetUnknownField('optional_nested_enum') self.assertEqual(self.all_fields.optional_nested_enum, value) @SkipIfCppImplementation def testRepeatedEnum(self): - value = self.GetField('repeated_nested_enum') + value = self.GetUnknownField('repeated_nested_enum') self.assertEqual(self.all_fields.repeated_nested_enum, value) @SkipIfCppImplementation def testVarint(self): - value = self.GetField('optional_int32') + value = self.GetUnknownField('optional_int32') self.assertEqual(self.all_fields.optional_int32, value) @SkipIfCppImplementation def testFixed32(self): - value = self.GetField('optional_fixed32') + value = self.GetUnknownField('optional_fixed32') self.assertEqual(self.all_fields.optional_fixed32, value) @SkipIfCppImplementation def testFixed64(self): - value = self.GetField('optional_fixed64') + value = self.GetUnknownField('optional_fixed64') self.assertEqual(self.all_fields.optional_fixed64, value) @SkipIfCppImplementation def testLengthDelimited(self): - value = self.GetField('optional_string') + value = self.GetUnknownField('optional_string') self.assertEqual(self.all_fields.optional_string, value) @SkipIfCppImplementation def testGroup(self): - value = self.GetField('optionalgroup') + value = self.GetUnknownField('optionalgroup') self.assertEqual(self.all_fields.optionalgroup, value) def testCopyFrom(self): @@ -241,43 +242,41 @@ class UnknownFieldsAccessorsTest(unittest.TestCase): self.assertEqual(message.SerializeToString(), self.all_fields_data) -class UnknownEnumValuesTest(unittest.TestCase): +class UnknownEnumValuesTest(BaseTestCase): def setUp(self): self.descriptor = missing_enum_values_pb2.TestEnumValues.DESCRIPTOR self.message = missing_enum_values_pb2.TestEnumValues() + # TestEnumValues.ZERO = 0, but does not exist in the other NestedEnum. self.message.optional_nested_enum = ( - missing_enum_values_pb2.TestEnumValues.ZERO) + missing_enum_values_pb2.TestEnumValues.ZERO) self.message.repeated_nested_enum.extend([ - missing_enum_values_pb2.TestEnumValues.ZERO, - missing_enum_values_pb2.TestEnumValues.ONE, - ]) + missing_enum_values_pb2.TestEnumValues.ZERO, + missing_enum_values_pb2.TestEnumValues.ONE, + ]) self.message.packed_nested_enum.extend([ - missing_enum_values_pb2.TestEnumValues.ZERO, - missing_enum_values_pb2.TestEnumValues.ONE, - ]) + missing_enum_values_pb2.TestEnumValues.ZERO, + missing_enum_values_pb2.TestEnumValues.ONE, + ]) self.message_data = self.message.SerializeToString() self.missing_message = missing_enum_values_pb2.TestMissingEnumValues() self.missing_message.ParseFromString(self.message_data) - if api_implementation.Type() != 'cpp': - # _unknown_fields is an implementation detail. - self.unknown_fields = self.missing_message._unknown_fields - # All the tests that use GetField() check an implementation detail of the - # Python implementation, which stores unknown fields as serialized strings. - # These tests are skipped by the C++ implementation: it's enough to check that - # the message is correctly serialized. + # GetUnknownField() checks a detail of the Python implementation, which stores + # unknown fields as serialized strings. It cannot be used by the C++ + # implementation: it's enough to check that the message is correctly + # serialized. - def GetField(self, name): + def GetUnknownField(self, name): field_descriptor = self.descriptor.fields_by_name[name] wire_type = type_checkers.FIELD_TYPE_TO_WIRE_TYPE[field_descriptor.type] field_tag = encoder.TagBytes(field_descriptor.number, wire_type) result_dict = {} - for tag_bytes, value in self.unknown_fields: + for tag_bytes, value in self.missing_message._unknown_fields: if tag_bytes == field_tag: decoder = missing_enum_values_pb2.TestEnumValues._decoders_by_tag[ - tag_bytes][0] + tag_bytes][0] decoder(value, 0, len(value), self.message, result_dict) return result_dict[field_descriptor] @@ -294,21 +293,39 @@ class UnknownEnumValuesTest(unittest.TestCase): # default value. self.assertEqual(missing.optional_nested_enum, 0) - @SkipIfCppImplementation def testUnknownEnumValue(self): + if api_implementation.Type() == 'cpp': + # The CPP implementation of protos (wrongly) allows unknown enum values + # for proto2. + self.assertTrue(self.missing_message.HasField('optional_nested_enum')) + self.assertEqual(self.message.optional_nested_enum, + self.missing_message.optional_nested_enum) + else: + # On the other hand, the Python implementation considers unknown values + # as unknown fields. This is the correct behavior. + self.assertFalse(self.missing_message.HasField('optional_nested_enum')) + value = self.GetUnknownField('optional_nested_enum') + self.assertEqual(self.message.optional_nested_enum, value) + self.missing_message.ClearField('optional_nested_enum') self.assertFalse(self.missing_message.HasField('optional_nested_enum')) - value = self.GetField('optional_nested_enum') - self.assertEqual(self.message.optional_nested_enum, value) - @SkipIfCppImplementation def testUnknownRepeatedEnumValue(self): - value = self.GetField('repeated_nested_enum') - self.assertEqual(self.message.repeated_nested_enum, value) + if api_implementation.Type() == 'cpp': + # For repeated enums, both implementations agree. + self.assertEqual([], self.missing_message.repeated_nested_enum) + else: + self.assertEqual([], self.missing_message.repeated_nested_enum) + value = self.GetUnknownField('repeated_nested_enum') + self.assertEqual(self.message.repeated_nested_enum, value) - @SkipIfCppImplementation def testUnknownPackedEnumValue(self): - value = self.GetField('packed_nested_enum') - self.assertEqual(self.message.packed_nested_enum, value) + if api_implementation.Type() == 'cpp': + # For repeated enums, both implementations agree. + self.assertEqual([], self.missing_message.packed_nested_enum) + else: + self.assertEqual([], self.missing_message.packed_nested_enum) + value = self.GetUnknownField('packed_nested_enum') + self.assertEqual(self.message.packed_nested_enum, value) def testRoundTrip(self): new_message = missing_enum_values_pb2.TestEnumValues() diff --git a/python/google/protobuf/internal/well_known_types.py b/python/google/protobuf/internal/well_known_types.py index 7c5dffd0..d631abee 100644 --- a/python/google/protobuf/internal/well_known_types.py +++ b/python/google/protobuf/internal/well_known_types.py @@ -53,6 +53,7 @@ _NANOS_PER_MICROSECOND = 1000 _MILLIS_PER_SECOND = 1000 _MICROS_PER_SECOND = 1000000 _SECONDS_PER_DAY = 24 * 3600 +_DURATION_SECONDS_MAX = 315576000000 class Error(Exception): @@ -247,6 +248,7 @@ class Duration(object): represent the exact Duration value. For example: "1s", "1.010s", "1.000000100s", "-3.100s" """ + _CheckDurationValid(self.seconds, self.nanos) if self.seconds < 0 or self.nanos < 0: result = '-' seconds = - self.seconds + int((0 - self.nanos) // 1e9) @@ -286,14 +288,17 @@ class Duration(object): try: pos = value.find('.') if pos == -1: - self.seconds = int(value[:-1]) - self.nanos = 0 + seconds = int(value[:-1]) + nanos = 0 else: - self.seconds = int(value[:pos]) + seconds = int(value[:pos]) if value[0] == '-': - self.nanos = int(round(float('-0{0}'.format(value[pos: -1])) *1e9)) + nanos = int(round(float('-0{0}'.format(value[pos: -1])) *1e9)) else: - self.nanos = int(round(float('0{0}'.format(value[pos: -1])) *1e9)) + nanos = int(round(float('0{0}'.format(value[pos: -1])) *1e9)) + _CheckDurationValid(seconds, nanos) + self.seconds = seconds + self.nanos = nanos except ValueError: raise ParseError( 'Couldn\'t parse duration: {0}.'.format(value)) @@ -359,6 +364,17 @@ class Duration(object): self.nanos = nanos +def _CheckDurationValid(seconds, nanos): + if seconds < -_DURATION_SECONDS_MAX or seconds > _DURATION_SECONDS_MAX: + raise Error( + 'Duration is not valid: Seconds {0} must be in range ' + '[-315576000000, 315576000000].'.format(seconds)) + if nanos <= -_NANOS_PER_SECOND or nanos >= _NANOS_PER_SECOND: + raise Error( + 'Duration is not valid: Nanos {0} must be in range ' + '[-999999999, 999999999].'.format(nanos)) + + def _RoundTowardZero(value, divider): """Truncates the remainder part after division.""" # For some languanges, the sign of the remainder is implementation @@ -379,13 +395,16 @@ class FieldMask(object): def ToJsonString(self): """Converts FieldMask to string according to proto3 JSON spec.""" - return ','.join(self.paths) + camelcase_paths = [] + for path in self.paths: + camelcase_paths.append(_SnakeCaseToCamelCase(path)) + return ','.join(camelcase_paths) def FromJsonString(self, value): """Converts string to FieldMask according to proto3 JSON spec.""" self.Clear() for path in value.split(','): - self.paths.append(path) + self.paths.append(_CamelCaseToSnakeCase(path)) def IsValidForDescriptor(self, message_descriptor): """Checks whether the FieldMask is valid for Message Descriptor.""" @@ -472,6 +491,48 @@ def _CheckFieldMaskMessage(message): message_descriptor.full_name)) +def _SnakeCaseToCamelCase(path_name): + """Converts a path name from snake_case to camelCase.""" + result = [] + after_underscore = False + for c in path_name: + if c.isupper(): + raise Error('Fail to print FieldMask to Json string: Path name ' + '{0} must not contain uppercase letters.'.format(path_name)) + if after_underscore: + if c.islower(): + result.append(c.upper()) + after_underscore = False + else: + raise Error('Fail to print FieldMask to Json string: The ' + 'character after a "_" must be a lowercase letter ' + 'in path name {0}.'.format(path_name)) + elif c == '_': + after_underscore = True + else: + result += c + + if after_underscore: + raise Error('Fail to print FieldMask to Json string: Trailing "_" ' + 'in path name {0}.'.format(path_name)) + return ''.join(result) + + +def _CamelCaseToSnakeCase(path_name): + """Converts a field name from camelCase to snake_case.""" + result = [] + for c in path_name: + if c == '_': + raise ParseError('Fail to parse FieldMask: Path name ' + '{0} must not contain "_"s.'.format(path_name)) + if c.isupper(): + result += '_' + result += c.lower() + else: + result += c + return ''.join(result) + + class _FieldMaskTree(object): """Represents a FieldMask in a tree structure. diff --git a/python/google/protobuf/internal/well_known_types_test.py b/python/google/protobuf/internal/well_known_types_test.py index 2f32ac99..077f630f 100644 --- a/python/google/protobuf/internal/well_known_types_test.py +++ b/python/google/protobuf/internal/well_known_types_test.py @@ -303,6 +303,25 @@ class TimeUtilTest(TimeUtilTestBase): well_known_types.ParseError, 'Couldn\'t parse duration: 1...2s.', message.FromJsonString, '1...2s') + text = '-315576000001.000000000s' + self.assertRaisesRegexp( + well_known_types.Error, + r'Duration is not valid\: Seconds -315576000001 must be in range' + r' \[-315576000000\, 315576000000\].', + message.FromJsonString, text) + text = '315576000001.000000000s' + self.assertRaisesRegexp( + well_known_types.Error, + r'Duration is not valid\: Seconds 315576000001 must be in range' + r' \[-315576000000\, 315576000000\].', + message.FromJsonString, text) + message.seconds = -315576000001 + message.nanos = 0 + self.assertRaisesRegexp( + well_known_types.Error, + r'Duration is not valid\: Seconds -315576000001 must be in range' + r' \[-315576000000\, 315576000000\].', + message.ToJsonString) class FieldMaskTest(unittest.TestCase): @@ -322,6 +341,20 @@ class FieldMaskTest(unittest.TestCase): mask.FromJsonString('foo,bar') self.assertEqual(['foo', 'bar'], mask.paths) + # Test camel case + mask.Clear() + mask.paths.append('foo_bar') + self.assertEqual('fooBar', mask.ToJsonString()) + mask.paths.append('bar_quz') + self.assertEqual('fooBar,barQuz', mask.ToJsonString()) + + mask.FromJsonString('') + self.assertEqual('', mask.ToJsonString()) + mask.FromJsonString('fooBar') + self.assertEqual(['foo_bar'], mask.paths) + mask.FromJsonString('fooBar,barQuz') + self.assertEqual(['foo_bar', 'bar_quz'], mask.paths) + def testDescriptorToFieldMask(self): mask = field_mask_pb2.FieldMask() msg_descriptor = unittest_pb2.TestAllTypes.DESCRIPTOR @@ -502,17 +535,68 @@ class FieldMaskTest(unittest.TestCase): nested_src.payload.repeated_int32.append(1234) nested_dst.payload.repeated_int32.append(5678) # Repeated fields will be appended by default. - mask.FromJsonString('payload.repeated_int32') + mask.FromJsonString('payload.repeatedInt32') mask.MergeMessage(nested_src, nested_dst) self.assertEqual(2, len(nested_dst.payload.repeated_int32)) self.assertEqual(5678, nested_dst.payload.repeated_int32[0]) self.assertEqual(1234, nested_dst.payload.repeated_int32[1]) # Change the behavior to replace repeated fields. - mask.FromJsonString('payload.repeated_int32') + mask.FromJsonString('payload.repeatedInt32') mask.MergeMessage(nested_src, nested_dst, False, True) self.assertEqual(1, len(nested_dst.payload.repeated_int32)) self.assertEqual(1234, nested_dst.payload.repeated_int32[0]) + def testSnakeCaseToCamelCase(self): + self.assertEqual('fooBar', + well_known_types._SnakeCaseToCamelCase('foo_bar')) + self.assertEqual('FooBar', + well_known_types._SnakeCaseToCamelCase('_foo_bar')) + self.assertEqual('foo3Bar', + well_known_types._SnakeCaseToCamelCase('foo3_bar')) + + # No uppercase letter is allowed. + self.assertRaisesRegexp( + well_known_types.Error, + 'Fail to print FieldMask to Json string: Path name Foo must ' + 'not contain uppercase letters.', + well_known_types._SnakeCaseToCamelCase, + 'Foo') + # Any character after a "_" must be a lowercase letter. + # 1. "_" cannot be followed by another "_". + # 2. "_" cannot be followed by a digit. + # 3. "_" cannot appear as the last character. + self.assertRaisesRegexp( + well_known_types.Error, + 'Fail to print FieldMask to Json string: The character after a ' + '"_" must be a lowercase letter in path name foo__bar.', + well_known_types._SnakeCaseToCamelCase, + 'foo__bar') + self.assertRaisesRegexp( + well_known_types.Error, + 'Fail to print FieldMask to Json string: The character after a ' + '"_" must be a lowercase letter in path name foo_3bar.', + well_known_types._SnakeCaseToCamelCase, + 'foo_3bar') + self.assertRaisesRegexp( + well_known_types.Error, + 'Fail to print FieldMask to Json string: Trailing "_" in path ' + 'name foo_bar_.', + well_known_types._SnakeCaseToCamelCase, + 'foo_bar_') + + def testCamelCaseToSnakeCase(self): + self.assertEqual('foo_bar', + well_known_types._CamelCaseToSnakeCase('fooBar')) + self.assertEqual('_foo_bar', + well_known_types._CamelCaseToSnakeCase('FooBar')) + self.assertEqual('foo3_bar', + well_known_types._CamelCaseToSnakeCase('foo3Bar')) + self.assertRaisesRegexp( + well_known_types.ParseError, + 'Fail to parse FieldMask: Path name foo_bar must not contain "_"s.', + well_known_types._CamelCaseToSnakeCase, + 'foo_bar') + class StructTest(unittest.TestCase): @@ -529,52 +613,52 @@ class StructTest(unittest.TestCase): struct_list.add_struct()['subkey2'] = 9 self.assertTrue(isinstance(struct, well_known_types.Struct)) - self.assertEquals(5, struct['key1']) - self.assertEquals('abc', struct['key2']) + self.assertEqual(5, struct['key1']) + self.assertEqual('abc', struct['key2']) self.assertIs(True, struct['key3']) - self.assertEquals(11, struct['key4']['subkey']) + self.assertEqual(11, struct['key4']['subkey']) inner_struct = struct_class() inner_struct['subkey2'] = 9 - self.assertEquals([6, 'seven', True, False, None, inner_struct], - list(struct['key5'].items())) + self.assertEqual([6, 'seven', True, False, None, inner_struct], + list(struct['key5'].items())) serialized = struct.SerializeToString() struct2 = struct_pb2.Struct() struct2.ParseFromString(serialized) - self.assertEquals(struct, struct2) + self.assertEqual(struct, struct2) self.assertTrue(isinstance(struct2, well_known_types.Struct)) - self.assertEquals(5, struct2['key1']) - self.assertEquals('abc', struct2['key2']) + self.assertEqual(5, struct2['key1']) + self.assertEqual('abc', struct2['key2']) self.assertIs(True, struct2['key3']) - self.assertEquals(11, struct2['key4']['subkey']) - self.assertEquals([6, 'seven', True, False, None, inner_struct], - list(struct2['key5'].items())) + self.assertEqual(11, struct2['key4']['subkey']) + self.assertEqual([6, 'seven', True, False, None, inner_struct], + list(struct2['key5'].items())) struct_list = struct2['key5'] - self.assertEquals(6, struct_list[0]) - self.assertEquals('seven', struct_list[1]) - self.assertEquals(True, struct_list[2]) - self.assertEquals(False, struct_list[3]) - self.assertEquals(None, struct_list[4]) - self.assertEquals(inner_struct, struct_list[5]) + self.assertEqual(6, struct_list[0]) + self.assertEqual('seven', struct_list[1]) + self.assertEqual(True, struct_list[2]) + self.assertEqual(False, struct_list[3]) + self.assertEqual(None, struct_list[4]) + self.assertEqual(inner_struct, struct_list[5]) struct_list[1] = 7 - self.assertEquals(7, struct_list[1]) + self.assertEqual(7, struct_list[1]) struct_list.add_list().extend([1, 'two', True, False, None]) - self.assertEquals([1, 'two', True, False, None], - list(struct_list[6].items())) + self.assertEqual([1, 'two', True, False, None], + list(struct_list[6].items())) text_serialized = str(struct) struct3 = struct_pb2.Struct() text_format.Merge(text_serialized, struct3) - self.assertEquals(struct, struct3) + self.assertEqual(struct, struct3) struct.get_or_create_struct('key3')['replace'] = 12 - self.assertEquals(12, struct['key3']['replace']) + self.assertEqual(12, struct['key3']['replace']) class AnyTest(unittest.TestCase): |