#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 Google.Protobuf; using Google.Protobuf.Reflection; using System.Buffers; using pb = global::Google.Protobuf; using pbr = global::Google.Protobuf.Reflection; using NUnit.Framework; using System.IO; using System; 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(() => { ParseContext.Initialize(new ReadOnlySequence(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(() => { WriteContext.Initialize(new ArrayBufferWriter(), 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(); /// Field number for the "bb" field. 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(); /// Field number for the "a" field. public const int AFieldNumber = 1; private LegacyGeneratedCodeMessageA a_; public LegacyGeneratedCodeMessageA A { get { return a_; } set { a_ = value; } } /// Field number for the "optional_int32" field. 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; } } } } } } }