diff options
author | Jon Skeet <jonskeet@google.com> | 2017-01-16 11:23:32 +0000 |
---|---|---|
committer | Jon Skeet <skeet@pobox.com> | 2017-01-19 06:46:04 +0000 |
commit | 047575f20cbdee8fb2f88d78a317ffe6133b93e6 (patch) | |
tree | 140117944c03c04c58086d451c09cad7fe2ca919 /csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs | |
parent | eed9951991c6ee7c629b5c4252444c372f7774fd (diff) | |
download | protobuf-047575f20cbdee8fb2f88d78a317ffe6133b93e6.tar.gz protobuf-047575f20cbdee8fb2f88d78a317ffe6133b93e6.tar.bz2 protobuf-047575f20cbdee8fb2f88d78a317ffe6133b93e6.zip |
Support custom options in C#
This consists of:
- Changing the codegen for the fixed set of options protos, to parse unknown fields instead of skipping them
- Add a new CustomOptions type in the C# support library
- Expose CustomOptions properties from the immutable proto wrappers in the support library
Only single-value options are currently supported, and fetching options values requires getting the type right
and knowing the field number. Both of these can be addressed at a later time.
Fixes #2143, at least as a first pass.
Diffstat (limited to 'csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs')
-rw-r--r-- | csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs b/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs new file mode 100644 index 00000000..b662e8b0 --- /dev/null +++ b/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs @@ -0,0 +1,267 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2017 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. +#endregion + +using Google.Protobuf.Reflection; +using Google.Protobuf.WellKnownTypes; +using NUnit.Framework; +using System.IO; +using System.Linq; +using UnitTest.Issues.TestProtos; +using static Google.Protobuf.WireFormat; +using static UnitTest.Issues.TestProtos.ComplexOptionType2.Types; +using static UnitTest.Issues.TestProtos.DummyMessageContainingEnum.Types; +using static Google.Protobuf.Test.Reflection.CustomOptionNumber; + +namespace Google.Protobuf.Test.Reflection +{ + // Internal enum to allow us to use "using static" for convenience. + // These are the options defined in unittest_custom_options_proto3.proto + internal enum CustomOptionNumber + { + FileOpt1 = 7736974, + MessageOpt1 = 7739036, + FieldOpt1 = 7740936, + EnumOpt1 = 7753576, + EnumValueOpt1 = 1560678, + ServiceOpt1 = 7887650, + MethodOpt1 = 7890860, + + // All message options... + BoolOpt = 7706090, + Int32Opt = 7705709, + Int64Opt = 7705542, + UInt32Opt = 7704880, + UInt64Opt = 7702367, + SInt32Opt = 7701568, + SInt64Opt = 7700863, + Fixed32Opt = 7700307, + Fixed64Opt = 7700194, + SFixed32Opt = 7698645, + SFixed64Opt = 7685475, + FloatOpt = 7675390, + DoubleOpt = 7673293, + StringOpt = 7673285, + BytesOpt = 7673238, + EnumOpt = 7673233, + MessageTypeOpt = 7665967, + + // Miscellaneous + ComplexOpt4 = 7633546, + ComplexOpt1 = 7646756, + ComplexOpt2 = 7636949, + ComplexOpt3 = 7636463, + + // Aggregates + AggregateFileOpt = 15478479, + AggregateMsgOpt = 15480088, + AggregateFieldOpt = 15481374, + AggregateEnumOpt = 15483218, + AggregateEnumValueOpt = 15486921, + AggregateServiceOpt = 15497145, + AggregateMethodOpt = 15512713, + } + + /// <summary> + /// The majority of the testing here is done via parsed descriptors. That's simpler to + /// achieve (and more important) than constructing a CodedInputStream manually. + /// </summary> + public class CustomOptionsTest + { + delegate bool OptionFetcher<T>(int field, out T value); + + [Test] + public void EmptyOptionsIsShared() + { + var structOptions = Struct.Descriptor.CustomOptions; + var timestampOptions = Struct.Descriptor.CustomOptions; + Assert.AreSame(structOptions, timestampOptions); + } + + [Test] + public void SimpleIntegerTest() + { + var stream = new MemoryStream(); + var output = new CodedOutputStream(stream); + output.WriteTag(MakeTag(1, WireType.Varint)); + output.WriteInt32(1234567); + output.Flush(); + stream.Position = 0; + var input = new CodedInputStream(stream); + input.ReadTag(); + + var options = CustomOptions.Empty; + options = options.ReadOrSkipUnknownField(input); + + int intValue; + Assert.True(options.TryGetInt32(1, out intValue)); + Assert.AreEqual(1234567, intValue); + + string stringValue; + // No ByteString stored values + Assert.False(options.TryGetString(1, out stringValue)); + // Nothing stored for field 2 + Assert.False(options.TryGetInt32(2, out intValue)); + } + + [Test] + public void SimpleStringTest() + { + var stream = new MemoryStream(); + var output = new CodedOutputStream(stream); + output.WriteTag(MakeTag(1, WireType.LengthDelimited)); + output.WriteString("value"); + output.Flush(); + stream.Position = 0; + var input = new CodedInputStream(stream); + input.ReadTag(); + + var options = CustomOptions.Empty; + options = options.ReadOrSkipUnknownField(input); + + string stringValue; + Assert.True(options.TryGetString(1, out stringValue)); + Assert.AreEqual("value", stringValue); + + int intValue; + // No numeric stored values + Assert.False(options.TryGetInt32(1, out intValue)); + // Nothing stored for field 2 + Assert.False(options.TryGetString(2, out stringValue)); + } + + [Test] + public void ScalarOptions() + { + var options = CustomOptionOtherValues.Descriptor.CustomOptions; + AssertOption(-100, options.TryGetInt32, Int32Opt); + AssertOption(12.3456789f, options.TryGetFloat, FloatOpt); + AssertOption(1.234567890123456789d, options.TryGetDouble, DoubleOpt); + AssertOption("Hello, \"World\"", options.TryGetString, StringOpt); + AssertOption(ByteString.CopyFromUtf8("Hello\0World"), options.TryGetBytes, BytesOpt); + AssertOption((int) TestEnumType.TestOptionEnumType2, options.TryGetInt32, EnumOpt); + } + + [Test] + public void MessageOptions() + { + var options = VariousComplexOptions.Descriptor.CustomOptions; + AssertOption(new ComplexOptionType1 { Foo = 42, Foo4 = { 99, 88 } }, options.TryGetMessage, ComplexOpt1); + AssertOption(new ComplexOptionType2 + { + Baz = 987, Bar = new ComplexOptionType1 { Foo = 743 }, + Fred = new ComplexOptionType4 { Waldo = 321 }, + Barney = { new ComplexOptionType4 { Waldo = 101 }, new ComplexOptionType4 { Waldo = 212 } } + }, + options.TryGetMessage, ComplexOpt2); + AssertOption(new ComplexOptionType3 { Qux = 9 }, options.TryGetMessage, ComplexOpt3); + } + + [Test] + public void OptionLocations() + { + var fileOptions = UnittestCustomOptionsProto3Reflection.Descriptor.CustomOptions; + AssertOption(9876543210UL, fileOptions.TryGetUInt64, FileOpt1); + + var messageOptions = TestMessageWithCustomOptions.Descriptor.CustomOptions; + AssertOption(-56, messageOptions.TryGetInt32, MessageOpt1); + + var fieldOptions = TestMessageWithCustomOptions.Descriptor.Fields["field1"] .CustomOptions; + AssertOption(8765432109UL, fieldOptions.TryGetFixed64, FieldOpt1); + + var enumOptions = TestMessageWithCustomOptions.Descriptor.EnumTypes[0].CustomOptions; + AssertOption(-789, enumOptions.TryGetSFixed32, EnumOpt1); + + var enumValueOptions = TestMessageWithCustomOptions.Descriptor.EnumTypes[0].FindValueByNumber(2).CustomOptions; + AssertOption(123, enumValueOptions.TryGetInt32, EnumValueOpt1); + + var service = UnittestCustomOptionsProto3Reflection.Descriptor.Services + .Single(s => s.Name == "TestServiceWithCustomOptions"); + var serviceOptions = service.CustomOptions; + AssertOption(-9876543210, serviceOptions.TryGetSInt64, ServiceOpt1); + + var methodOptions = service.Methods[0].CustomOptions; + AssertOption((int) UnitTest.Issues.TestProtos.MethodOpt1.Val2, methodOptions.TryGetInt32, CustomOptionNumber.MethodOpt1); + } + + [Test] + public void MinValues() + { + var options = CustomOptionMinIntegerValues.Descriptor.CustomOptions; + AssertOption(false, options.TryGetBool, BoolOpt); + AssertOption(int.MinValue, options.TryGetInt32, Int32Opt); + AssertOption(long.MinValue, options.TryGetInt64, Int64Opt); + AssertOption(uint.MinValue, options.TryGetUInt32, UInt32Opt); + AssertOption(ulong.MinValue, options.TryGetUInt64, UInt64Opt); + AssertOption(int.MinValue, options.TryGetSInt32, SInt32Opt); + AssertOption(long.MinValue, options.TryGetSInt64, SInt64Opt); + AssertOption(uint.MinValue, options.TryGetUInt32, Fixed32Opt); + AssertOption(ulong.MinValue, options.TryGetUInt64, Fixed64Opt); + AssertOption(int.MinValue, options.TryGetInt32, SFixed32Opt); + AssertOption(long.MinValue, options.TryGetInt64, SFixed64Opt); + } + + [Test] + public void MaxValues() + { + var options = CustomOptionMaxIntegerValues.Descriptor.CustomOptions; + AssertOption(true, options.TryGetBool, BoolOpt); + AssertOption(int.MaxValue, options.TryGetInt32, Int32Opt); + AssertOption(long.MaxValue, options.TryGetInt64, Int64Opt); + AssertOption(uint.MaxValue, options.TryGetUInt32, UInt32Opt); + AssertOption(ulong.MaxValue, options.TryGetUInt64, UInt64Opt); + AssertOption(int.MaxValue, options.TryGetSInt32, SInt32Opt); + AssertOption(long.MaxValue, options.TryGetSInt64, SInt64Opt); + AssertOption(uint.MaxValue, options.TryGetFixed32, Fixed32Opt); + AssertOption(ulong.MaxValue, options.TryGetFixed64, Fixed64Opt); + AssertOption(int.MaxValue, options.TryGetSFixed32, SFixed32Opt); + AssertOption(long.MaxValue, options.TryGetSFixed64, SFixed64Opt); + } + + [Test] + public void AggregateOptions() + { + // Just two examples + var messageOptions = AggregateMessage.Descriptor.CustomOptions; + AssertOption(new Aggregate { I = 101, S = "MessageAnnotation" }, messageOptions.TryGetMessage, AggregateMsgOpt); + + var fieldOptions = AggregateMessage.Descriptor.Fields["fieldname"].CustomOptions; + AssertOption(new Aggregate { S = "FieldAnnotation" }, fieldOptions.TryGetMessage, AggregateFieldOpt); + } + + private void AssertOption<T>(T expected, OptionFetcher<T> fetcher, CustomOptionNumber field) + { + T actual; + Assert.IsTrue(fetcher((int) field, out actual)); + Assert.AreEqual(expected, actual); + } + } +} |