| #region Copyright notice and license | |
| // Protocol Buffers - Google's data interchange format | |
| // Copyright 2008 Google Inc. All rights reserved. | |
| // | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file or at | |
| // https://developers.google.com/open-source/licenses/bsd | |
| #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; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } |