浏览代码

Add tests for field presence and default values
Adjust APIs for extensions to properly return default values for extensions
Fix issues with IsInitialized and proto2 field reflection

Sydney Acksman 6 年之前
父节点
当前提交
8b7fb7d0f4

+ 6 - 0
csharp/protos/unittest.proto

@@ -764,6 +764,12 @@ message TestRequiredOneof {
   }
 }
 
+message TestRequiredMap {
+  map<int32, NestedMessage> foo = 1;
+  message NestedMessage {
+    required int32 required_int32 = 1;
+  }
+}
 
 // Test messages for packed fields
 

+ 146 - 0
csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs

@@ -33,6 +33,7 @@
 using System;
 using System.IO;
 using Google.Protobuf.TestProtos;
+using Proto2 = Google.Protobuf.TestProtos.Proto2;
 using NUnit.Framework;
 using System.Collections;
 using System.Collections.Generic;
@@ -111,6 +112,50 @@ namespace Google.Protobuf
             Assert.AreEqual("", message.OneofString);
             Assert.AreEqual(ByteString.Empty, message.OneofBytes);
             Assert.IsNull(message.OneofNestedMessage);
+
+            // proto2 default values
+            var message2 = new Proto2.TestAllTypes();
+            Assert.AreEqual(true, message2.DefaultBool);
+            Assert.AreEqual(ByteString.CopyFromUtf8("world"), message2.DefaultBytes);
+            Assert.AreEqual("123", message2.DefaultCord);
+            Assert.AreEqual(52e3, message2.DefaultDouble);
+            Assert.AreEqual(47, message2.DefaultFixed32);
+            Assert.AreEqual(48, message2.DefaultFixed64);
+            Assert.AreEqual(51.5, message2.DefaultFloat);
+            Assert.AreEqual(Proto2.ForeignEnum.ForeignBar, message2.DefaultForeignEnum);
+            Assert.AreEqual(Proto2.ImportEnum.ImportBar, message2.DefaultImportEnum);
+            Assert.AreEqual(41, message2.DefaultInt32);
+            Assert.AreEqual(42, message2.DefaultInt64);
+            Assert.AreEqual(Proto2.TestAllTypes.Types.NestedEnum.Bar, message2.DefaultNestedEnum);
+            Assert.AreEqual(49, message2.DefaultSfixed32);
+            Assert.AreEqual(-50, message2.DefaultSfixed64);
+            Assert.AreEqual(-45, message2.DefaultSint32);
+            Assert.AreEqual(46, message2.DefaultSint64);
+            Assert.AreEqual("hello", message2.DefaultString);
+            Assert.AreEqual("abc", message2.DefaultStringPiece);
+            Assert.AreEqual(43, message2.DefaultUint32);
+            Assert.AreEqual(44, message2.DefaultUint64);
+
+            Assert.False(message2.HasDefaultBool);
+            Assert.False(message2.HasDefaultBytes);
+            Assert.False(message2.HasDefaultCord);
+            Assert.False(message2.HasDefaultDouble);
+            Assert.False(message2.HasDefaultFixed32);
+            Assert.False(message2.HasDefaultFixed64);
+            Assert.False(message2.HasDefaultFloat);
+            Assert.False(message2.HasDefaultForeignEnum);
+            Assert.False(message2.HasDefaultImportEnum);
+            Assert.False(message2.HasDefaultInt32);
+            Assert.False(message2.HasDefaultInt64);
+            Assert.False(message2.HasDefaultNestedEnum);
+            Assert.False(message2.HasDefaultSfixed32);
+            Assert.False(message2.HasDefaultSfixed64);
+            Assert.False(message2.HasDefaultSint32);
+            Assert.False(message2.HasDefaultSint64);
+            Assert.False(message2.HasDefaultString);
+            Assert.False(message2.HasDefaultStringPiece);
+            Assert.False(message2.HasDefaultUint32);
+            Assert.False(message2.HasDefaultUint64);
         }
 
         [Test]
@@ -123,6 +168,107 @@ namespace Google.Protobuf
             Assert.Throws<ArgumentNullException>(() => message.OneofBytes = null);
         }
 
+        [Test]
+        public void FieldPresence()
+        {
+            var message = new Proto2.TestAllTypes();
+
+            Assert.False(message.HasOptionalBool);
+            Assert.False(message.OptionalBool);
+
+            message.OptionalBool = true;
+
+            Assert.True(message.HasOptionalBool);
+            Assert.True(message.OptionalBool);
+
+            message.OptionalBool = false;
+
+            Assert.True(message.HasOptionalBool);
+            Assert.False(message.OptionalBool);
+
+            message.ClearOptionalBool();
+
+            Assert.False(message.HasOptionalBool);
+            Assert.False(message.OptionalBool);
+
+            Assert.False(message.HasDefaultBool);
+            Assert.True(message.DefaultBool);
+
+            message.DefaultBool = false;
+
+            Assert.True(message.HasDefaultBool);
+            Assert.False(message.DefaultBool);
+
+            message.DefaultBool = true;
+
+            Assert.True(message.HasDefaultBool);
+            Assert.True(message.DefaultBool);
+
+            message.ClearDefaultBool();
+
+            Assert.False(message.HasDefaultBool);
+            Assert.True(message.DefaultBool);
+        }
+
+        [Test]
+        public void RequiredFields()
+        {
+            var message = new Proto2.TestRequired();
+            Assert.False(message.IsInitialized());
+
+            message.A = 1;
+            message.B = 2;
+            message.C = 3;
+
+            Assert.True(message.IsInitialized());
+        }
+
+        [Test]
+        public void RequiredFieldsInExtensions()
+        {
+            var message = new Proto2.TestAllExtensions();
+            Assert.True(message.IsInitialized());
+
+            message.SetExtension(Proto2.TestRequired.Extensions.Single, new Proto2.TestRequired());
+
+            Assert.False(message.IsInitialized());
+
+            var extensionMessage = message.GetExtension(Proto2.TestRequired.Extensions.Single);
+            extensionMessage.A = 1;
+            extensionMessage.B = 2;
+            extensionMessage.C = 3;
+
+            Assert.True(message.IsInitialized());
+
+            message.RegisterExtension(Proto2.TestRequired.Extensions.Multi);
+
+            Assert.True(message.IsInitialized());
+
+            message.GetExtension(Proto2.TestRequired.Extensions.Multi).Add(new Proto2.TestRequired());
+
+            Assert.False(message.IsInitialized());
+
+            extensionMessage = message.GetExtension(Proto2.TestRequired.Extensions.Multi)[0];
+            extensionMessage.A = 1;
+            extensionMessage.B = 2;
+            extensionMessage.C = 3;
+
+            Assert.True(message.IsInitialized());
+        }
+
+        [Test]
+        public void RequiredFieldInNestedMessageMapValue()
+        {
+            var message = new Proto2.TestRequiredMap();
+            message.Foo.Add(0, new Proto2.TestRequiredMap.Types.NestedMessage());
+
+            Assert.False(message.IsInitialized());
+
+            message.Foo[0].RequiredInt32 = 12;
+
+            Assert.True(message.IsInitialized());
+        }
+
         [Test]
         public void RoundTrip_Empty()
         {

+ 52 - 0
csharp/src/Google.Protobuf.Test/SampleMessages.cs

@@ -32,6 +32,7 @@
 
 using System;
 using Google.Protobuf.TestProtos;
+using Proto2 = Google.Protobuf.TestProtos.Proto2;
 
 namespace Google.Protobuf
 {
@@ -95,5 +96,56 @@ namespace Google.Protobuf
                 OneofString = "Oneof string"
             };
         }
+
+        public static Proto2.TestAllTypes CreateFullTestAllTypesProto2()
+        {
+            return new Proto2.TestAllTypes
+            {
+                OptionalBool = true,
+                OptionalBytes = ByteString.CopyFrom(1, 2, 3, 4),
+                OptionalDouble = 23.5,
+                OptionalFixed32 = 23,
+                OptionalFixed64 = 1234567890123,
+                OptionalFloat = 12.25f,
+                OptionalForeignEnum = Proto2.ForeignEnum.ForeignBar,
+                OptionalForeignMessage = new Proto2.ForeignMessage { C = 10 },
+                OptionalImportEnum = Proto2.ImportEnum.ImportBaz,
+                OptionalImportMessage = new Proto2.ImportMessage { D = 20 },
+                OptionalInt32 = 100,
+                OptionalInt64 = 3210987654321,
+                OptionalNestedEnum = Proto2.TestAllTypes.Types.NestedEnum.Foo,
+                OptionalNestedMessage = new Proto2.TestAllTypes.Types.NestedMessage { Bb = 35 },
+                OptionalPublicImportMessage = new Proto2.PublicImportMessage { E = 54 },
+                OptionalSfixed32 = -123,
+                OptionalSfixed64 = -12345678901234,
+                OptionalSint32 = -456,
+                OptionalSint64 = -12345678901235,
+                OptionalString = "test",
+                OptionalUint32 = UInt32.MaxValue,
+                OptionalUint64 = UInt64.MaxValue,
+                RepeatedBool = { true, false },
+                RepeatedBytes = { ByteString.CopyFrom(1, 2, 3, 4), ByteString.CopyFrom(5, 6), ByteString.CopyFrom(new byte[1000]) },
+                RepeatedDouble = { -12.25, 23.5 },
+                RepeatedFixed32 = { UInt32.MaxValue, 23 },
+                RepeatedFixed64 = { UInt64.MaxValue, 1234567890123 },
+                RepeatedFloat = { 100f, 12.25f },
+                RepeatedForeignEnum = { Proto2.ForeignEnum.ForeignFoo, Proto2.ForeignEnum.ForeignBar },
+                RepeatedForeignMessage = { new Proto2.ForeignMessage(), new Proto2.ForeignMessage { C = 10 } },
+                RepeatedImportEnum = { Proto2.ImportEnum.ImportBaz, Proto2.ImportEnum.ImportFoo },
+                RepeatedImportMessage = { new Proto2.ImportMessage { D = 20 }, new Proto2.ImportMessage { D = 25 } },
+                RepeatedInt32 = { 100, 200 },
+                RepeatedInt64 = { 3210987654321, Int64.MaxValue },
+                RepeatedNestedEnum = { Proto2.TestAllTypes.Types.NestedEnum.Foo, Proto2.TestAllTypes.Types.NestedEnum.Neg },
+                RepeatedNestedMessage = { new Proto2.TestAllTypes.Types.NestedMessage { Bb = 35 }, new Proto2.TestAllTypes.Types.NestedMessage { Bb = 10 } },
+                RepeatedSfixed32 = { -123, 123 },
+                RepeatedSfixed64 = { -12345678901234, 12345678901234 },
+                RepeatedSint32 = { -456, 100 },
+                RepeatedSint64 = { -12345678901235, 123 },
+                RepeatedString = { "foo", "bar" },
+                RepeatedUint32 = { UInt32.MaxValue, UInt32.MinValue },
+                RepeatedUint64 = { UInt64.MaxValue, UInt32.MinValue },
+                OneofString = "Oneof string"
+            };
+        }
     }
 }

文件差异内容过多而无法显示
+ 372 - 367
csharp/src/Google.Protobuf.Test/TestProtos/Unittest.cs


+ 2 - 0
csharp/src/Google.Protobuf/Extension.cs

@@ -79,6 +79,8 @@ namespace Google.Protobuf
 
         internal override Type TargetType => typeof(TTarget);
 
+        internal TValue DefaultValue => codec.DefaultValue;
+
         internal override IExtensionValue CreateValue()
         {
             return new ExtensionValue<TValue>(codec);

+ 5 - 0
csharp/src/Google.Protobuf/ExtensionSet.cs

@@ -330,5 +330,10 @@ namespace Google.Protobuf
                 value.WriteTo(stream);
             }
         }
+
+        internal bool IsInitialized()
+        {
+            return ValuesByNumber.Values.All(v => v.IsInitialized());
+        }
     }
 }

+ 12 - 0
csharp/src/Google.Protobuf/ExtensionValue.cs

@@ -32,6 +32,7 @@
 
 using Google.Protobuf.Collections;
 using System;
+using System.Linq;
 
 namespace Google.Protobuf
 {
@@ -41,6 +42,7 @@ namespace Google.Protobuf
         void MergeFrom(IExtensionValue value);
         void WriteTo(CodedOutputStream output);
         int CalculateSize();
+        bool IsInitialized();
     }
 
     internal sealed class ExtensionValue<T> : IExtensionValue
@@ -137,6 +139,11 @@ namespace Google.Protobuf
         }
 
         public bool HasValue => hasValue;
+
+        public bool IsInitialized()
+        {
+            return HasValue && field is IMessage && (field as IMessage).IsInitialized();
+        }
     }
 
     internal sealed class RepeatedExtensionValue<T> : IExtensionValue
@@ -203,5 +210,10 @@ namespace Google.Protobuf
         }
 
         public RepeatedField<T> GetValue() => field;
+
+        public bool IsInitialized()
+        {
+            return field.All(m => m is IMessage && (m as IMessage).IsInitialized());
+        }
     }
 }

+ 16 - 3
csharp/src/Google.Protobuf/MessageExtensions.cs

@@ -148,11 +148,16 @@ namespace Google.Protobuf
         /// </summary>
         public static bool IsInitialized(this IMessage message)
         {
-            if (message.Descriptor.File.Proto.Syntax != "proto2")
+            if (message.Descriptor.File.Proto.Syntax == "proto3")
             {
                 return true;
             }
 
+            if (!message.Descriptor.GetIsExtensionsInitialized(message))
+            {
+                return false;
+            }
+
             return message.Descriptor
                 .Fields
                 .InDeclarationOrder()
@@ -160,8 +165,16 @@ namespace Google.Protobuf
                 {
                     if (f.IsMap)
                     {
-                        var map = (IDictionary)f.Accessor.GetValue(message);
-                        return map.Values.OfType<IMessage>().All(IsInitialized);
+                        var valueField = f.MessageType.Fields[2];
+                        if (valueField.FieldType == FieldType.Message)
+                        {
+                            var map = (IDictionary)f.Accessor.GetValue(message);
+                            return map.Values.Cast<IMessage>().All(IsInitialized);
+                        }
+                        else
+                        {
+                            return true;
+                        }
                     }
                     else if (f.IsRepeated && f.FieldType == FieldType.Message || f.FieldType == FieldType.Group)
                     {

+ 22 - 0
csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs

@@ -34,6 +34,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Linq;
+using System.Reflection;
 #if NET35
 // Needed for ReadOnlyDictionary, which does not exist in .NET 3.5
 using Google.Protobuf.Collections;
@@ -63,6 +64,7 @@ namespace Google.Protobuf.Reflection
         private readonly IList<FieldDescriptor> fieldsInDeclarationOrder;
         private readonly IList<FieldDescriptor> fieldsInNumberOrder;
         private readonly IDictionary<string, FieldDescriptor> jsonFieldMap;
+        private Func<IMessage, bool> extensionSetIsInitialized;
 
         internal MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedClrTypeInfo generatedCodeInfo)
             : base(file, file.ComputeFullName(parent, proto.Name), typeIndex)
@@ -134,6 +136,26 @@ namespace Google.Protobuf.Reflection
 
         internal DescriptorProto Proto { get; }
 
+        internal bool GetIsExtensionsInitialized(IMessage message)
+        {
+            if (!object.ReferenceEquals(message.Descriptor, this))
+            {
+                throw new InvalidOperationException("message's descriptor reference does not match this");
+            }
+
+            if (Proto.ExtensionRange.Count == 0)
+            {
+                return true;
+            }
+
+            if (extensionSetIsInitialized == null)
+            {
+                extensionSetIsInitialized = ReflectionUtil.CreateIsInitializedCaller(ClrType);
+            }
+
+            return extensionSetIsInitialized(message);
+        }
+
         /// <summary>
         /// The CLR type used to represent message instances from this descriptor.
         /// </summary>

+ 26 - 1
csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs

@@ -71,7 +71,7 @@ namespace Google.Protobuf.Reflection
 
         /// <summary>
         /// Empty Type[] used when calling GetProperty to force property instead of indexer fetching.
-        /// </summary>
+        /// </summary>getFieldFunc
         internal static readonly Type[] EmptyTypes = new Type[0];
 
         /// <summary>
@@ -115,6 +115,9 @@ namespace Google.Protobuf.Reflection
         internal static Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method) =>
             GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageBool(method);
 
+        internal static Func<IMessage, bool> CreateIsInitializedCaller(Type msg) =>
+            ((IExtensionSetReflector)Activator.CreateInstance(typeof(ExtensionSetReflector<>).MakeGenericType(msg))).CreateIsInitializedCaller();
+
         /// <summary>
         /// Creates a delegate which will execute the given method after casting the first argument to
         /// the type that declares the method, and the second argument to the first parameter type of the method.
@@ -150,6 +153,11 @@ namespace Google.Protobuf.Reflection
             void ClearExtension(IMessage message);
         }
 
+        private interface IExtensionSetReflector
+        {
+            Func<IMessage, bool> CreateIsInitializedCaller();
+        }
+
         private class ReflectionHelper<T1, T2> : IReflectionHelper
         {
 
@@ -300,6 +308,23 @@ namespace Google.Protobuf.Reflection
             }
         }
 
+        private class ExtensionSetReflector<T1> : IExtensionSetReflector where T1 : IExtendableMessage<T1>
+        {
+            public Func<IMessage, bool> CreateIsInitializedCaller()
+            {
+                var field = typeof(T1).GetTypeInfo().GetDeclaredField("_extensions");
+                var initializedFunc = (Func<ExtensionSet<T1>, bool>)
+                    typeof(ExtensionSet<T1>)
+                        .GetTypeInfo()
+                        .GetDeclaredMethod("IsInitialized")
+                        .CreateDelegate(typeof(Func<ExtensionSet<T1>, bool>));
+                return (m) => {
+                    var set = field.GetValue(m) as ExtensionSet<T1>;
+                    return set == null || initializedFunc(set);
+                };
+            }
+        }
+
         // Runtime compatibility checking code - see ReflectionHelper<T1, T2>.CreateFuncIMessageInt32 for
         // details about why we're doing this.
 

+ 14 - 14
csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs

@@ -57,20 +57,7 @@ namespace Google.Protobuf.Reflection
                 throw new ArgumentException("Not all required properties/methods available");
             }
             setValueDelegate = ReflectionUtil.CreateActionIMessageObject(property.GetSetMethod());
-            if (descriptor.File.Proto.Syntax == "proto2")
-            {
-                MethodInfo hasMethod = property.DeclaringType.GetRuntimeProperty("Has" + property.Name).GetMethod;
-                if (hasMethod == null) {
-                  throw new ArgumentException("Not all required properties/methods are available");
-                }
-                hasDelegate = ReflectionUtil.CreateFuncIMessageBool(hasMethod);
-                MethodInfo clearMethod = property.DeclaringType.GetRuntimeMethod("Clear" + property.Name, ReflectionUtil.EmptyTypes);
-                if (clearMethod == null) {
-                  throw new ArgumentException("Not all required properties/methods are available");
-                }
-                clearDelegate = ReflectionUtil.CreateActionIMessage(clearMethod);
-            }
-            else
+            if (descriptor.File.Proto.Syntax == "proto3")
             {
                 hasDelegate = message => {
                   throw new InvalidOperationException("HasValue is not implemented for proto3 fields");
@@ -85,6 +72,19 @@ namespace Google.Protobuf.Reflection
                     : Activator.CreateInstance(clrType);
                 clearDelegate = message => SetValue(message, defaultValue);
             }
+            else
+            {
+                MethodInfo hasMethod = property.DeclaringType.GetRuntimeProperty("Has" + property.Name).GetMethod;
+                if (hasMethod == null) {
+                  throw new ArgumentException("Not all required properties/methods are available");
+                }
+                hasDelegate = ReflectionUtil.CreateFuncIMessageBool(hasMethod);
+                MethodInfo clearMethod = property.DeclaringType.GetRuntimeMethod("Clear" + property.Name, ReflectionUtil.EmptyTypes);
+                if (clearMethod == null) {
+                  throw new ArgumentException("Not all required properties/methods are available");
+                }
+                clearDelegate = ReflectionUtil.CreateActionIMessage(clearMethod);
+            }
         }
 
         public override void Clear(IMessage message)

部分文件因为文件数量过多而无法显示