| #region Copyright notice and license |
| // 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. |
| #endregion |
| |
| using System.Buffers; |
| using pb = Google.Protobuf; |
| using pbr = Google.Protobuf.Reflection; |
| using NUnit.Framework; |
| using System.IO; |
| using Google.Protobuf.Buffers; |
| |
| namespace Google.Protobuf |
| { |
| public class LegacyGeneratedCodeTest |
| { |
| [Test] |
| public void IntermixingOfNewAndLegacyGeneratedCodeWorksWithCodedInputStream() |
| { |
| var message = new ParseContextEnabledMessageB |
| { |
| A = new LegacyGeneratedCodeMessageA |
| { |
| Bb = new ParseContextEnabledMessageB { OptionalInt32 = 12345 } |
| }, |
| OptionalInt32 = 6789 |
| }; |
| var data = message.ToByteArray(); |
| |
| // when parsing started using CodedInputStream and a message with legacy generated code |
| // is encountered somewhere in the parse tree, we still need to be able to use its |
| // MergeFrom(CodedInputStream) method to parse correctly. |
| var codedInput = new CodedInputStream(data); |
| var parsed = new ParseContextEnabledMessageB(); |
| codedInput.ReadRawMessage(parsed); |
| Assert.IsTrue(codedInput.IsAtEnd); |
| |
| Assert.AreEqual(12345, parsed.A.Bb.OptionalInt32); |
| Assert.AreEqual(6789, parsed.OptionalInt32); |
| } |
| |
| [Test] |
| public void LegacyGeneratedCodeThrowsWithReadOnlySequence() |
| { |
| var message = new ParseContextEnabledMessageB |
| { |
| A = new LegacyGeneratedCodeMessageA |
| { |
| Bb = new ParseContextEnabledMessageB { OptionalInt32 = 12345 } |
| }, |
| OptionalInt32 = 6789 |
| }; |
| var data = message.ToByteArray(); |
| |
| // if parsing started using ReadOnlySequence and we don't have a CodedInputStream |
| // instance at hand, we cannot fall back to the legacy MergeFrom(CodedInputStream) |
| // method and parsing will fail. As a consequence, one can only use parsing |
| // from ReadOnlySequence if all the messages in the parsing tree have their generated |
| // code up to date. |
| var exception = Assert.Throws<InvalidProtocolBufferException>(() => |
| { |
| ParseContext.Initialize(new ReadOnlySequence<byte>(data), out ParseContext parseCtx); |
| var parsed = new ParseContextEnabledMessageB(); |
| ParsingPrimitivesMessages.ReadRawMessage(ref parseCtx, parsed); |
| }); |
| Assert.AreEqual($"Message {typeof(LegacyGeneratedCodeMessageA).Name} doesn't provide the generated method that enables ParseContext-based parsing. You might need to regenerate the generated protobuf code.", exception.Message); |
| } |
| |
| [Test] |
| public void IntermixingOfNewAndLegacyGeneratedCodeWorksWithCodedOutputStream() |
| { |
| // when serialization started using CodedOutputStream and a message with legacy generated code |
| // is encountered somewhere in the parse tree, we still need to be able to use its |
| // WriteTo(CodedOutputStream) method to serialize correctly. |
| var ms = new MemoryStream(); |
| var codedOutput = new CodedOutputStream(ms); |
| var message = new ParseContextEnabledMessageB |
| { |
| A = new LegacyGeneratedCodeMessageA |
| { |
| Bb = new ParseContextEnabledMessageB { OptionalInt32 = 12345 } |
| }, |
| OptionalInt32 = 6789 |
| }; |
| message.WriteTo(codedOutput); |
| codedOutput.Flush(); |
| |
| var codedInput = new CodedInputStream(ms.ToArray()); |
| var parsed = new ParseContextEnabledMessageB(); |
| codedInput.ReadRawMessage(parsed); |
| Assert.IsTrue(codedInput.IsAtEnd); |
| |
| Assert.AreEqual(12345, parsed.A.Bb.OptionalInt32); |
| Assert.AreEqual(6789, parsed.OptionalInt32); |
| } |
| |
| [Test] |
| public void LegacyGeneratedCodeThrowsWithIBufferWriter() |
| { |
| // if serialization started using IBufferWriter and we don't have a CodedOutputStream |
| // instance at hand, we cannot fall back to the legacy WriteTo(CodedOutputStream) |
| // method and serializatin will fail. As a consequence, one can only use serialization |
| // to IBufferWriter if all the messages in the parsing tree have their generated |
| // code up to date. |
| var message = new ParseContextEnabledMessageB |
| { |
| A = new LegacyGeneratedCodeMessageA |
| { |
| Bb = new ParseContextEnabledMessageB { OptionalInt32 = 12345 } |
| }, |
| OptionalInt32 = 6789 |
| }; |
| var exception = Assert.Throws<InvalidProtocolBufferException>(() => |
| { |
| WriteContext.Initialize(new TestArrayBufferWriter<byte>(), out WriteContext writeCtx); |
| ((IBufferMessage)message).InternalWriteTo(ref writeCtx); |
| }); |
| Assert.AreEqual($"Message {typeof(LegacyGeneratedCodeMessageA).Name} doesn't provide the generated method that enables WriteContext-based serialization. You might need to regenerate the generated protobuf code.", exception.Message); |
| } |
| |
| // hand-modified version of a generated message that only provides the legacy |
| // MergeFrom(CodedInputStream) method and doesn't implement IBufferMessage. |
| private sealed partial class LegacyGeneratedCodeMessageA : pb::IMessage { |
| private pb::UnknownFieldSet _unknownFields; |
| |
| pbr::MessageDescriptor pb::IMessage.Descriptor => throw new System.NotImplementedException(); |
| |
| /// <summary>Field number for the "bb" field.</summary> |
| public const int BbFieldNumber = 1; |
| private ParseContextEnabledMessageB bb_; |
| public ParseContextEnabledMessageB Bb { |
| get { return bb_; } |
| set { |
| bb_ = value; |
| } |
| } |
| |
| public void WriteTo(pb::CodedOutputStream output) { |
| if (bb_ != null) { |
| output.WriteRawTag(10); |
| output.WriteMessage(Bb); |
| } |
| if (_unknownFields != null) { |
| _unknownFields.WriteTo(output); |
| } |
| } |
| |
| public int CalculateSize() { |
| int size = 0; |
| if (bb_ != null) { |
| size += 1 + pb::CodedOutputStream.ComputeMessageSize(Bb); |
| } |
| if (_unknownFields != null) { |
| size += _unknownFields.CalculateSize(); |
| } |
| return size; |
| } |
| |
| public void MergeFrom(pb::CodedInputStream input) { |
| uint tag; |
| while ((tag = input.ReadTag()) != 0) { |
| switch(tag) { |
| default: |
| _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); |
| break; |
| case 10: { |
| if (bb_ == null) { |
| Bb = new ParseContextEnabledMessageB(); |
| } |
| input.ReadMessage(Bb); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| // hand-modified version of a generated message that does provide |
| // the new InternalMergeFrom(ref ParseContext) method. |
| private sealed partial class ParseContextEnabledMessageB : pb::IBufferMessage { |
| private pb::UnknownFieldSet _unknownFields; |
| |
| pbr::MessageDescriptor pb::IMessage.Descriptor => throw new System.NotImplementedException(); |
| |
| /// <summary>Field number for the "a" field.</summary> |
| public const int AFieldNumber = 1; |
| private LegacyGeneratedCodeMessageA a_; |
| public LegacyGeneratedCodeMessageA A { |
| get { return a_; } |
| set { |
| a_ = value; |
| } |
| } |
| |
| /// <summary>Field number for the "optional_int32" field.</summary> |
| public const int OptionalInt32FieldNumber = 2; |
| private int optionalInt32_; |
| public int OptionalInt32 { |
| get { return optionalInt32_; } |
| set { |
| optionalInt32_ = value; |
| } |
| } |
| |
| public void WriteTo(pb::CodedOutputStream output) { |
| output.WriteRawMessage(this); |
| } |
| |
| void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) |
| { |
| if (a_ != null) |
| { |
| output.WriteRawTag(10); |
| output.WriteMessage(A); |
| } |
| if (OptionalInt32 != 0) |
| { |
| output.WriteRawTag(16); |
| output.WriteInt32(OptionalInt32); |
| } |
| if (_unknownFields != null) |
| { |
| _unknownFields.WriteTo(ref output); |
| } |
| } |
| |
| public int CalculateSize() { |
| int size = 0; |
| if (a_ != null) { |
| size += 1 + pb::CodedOutputStream.ComputeMessageSize(A); |
| } |
| if (OptionalInt32 != 0) { |
| size += 1 + pb::CodedOutputStream.ComputeInt32Size(OptionalInt32); |
| } |
| if (_unknownFields != null) { |
| size += _unknownFields.CalculateSize(); |
| } |
| return size; |
| } |
| public void MergeFrom(pb::CodedInputStream input) { |
| input.ReadRawMessage(this); |
| } |
| |
| void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { |
| uint tag; |
| while ((tag = input.ReadTag()) != 0) { |
| switch(tag) { |
| default: |
| _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); |
| break; |
| case 10: { |
| if (a_ == null) { |
| A = new LegacyGeneratedCodeMessageA(); |
| } |
| input.ReadMessage(A); |
| break; |
| } |
| case 16: { |
| OptionalInt32 = input.ReadInt32(); |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |