1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file or at 7 // https://developers.google.com/open-source/licenses/bsd 8 #endregion 9 10 using System.Buffers; 11 using pb = Google.Protobuf; 12 using pbr = Google.Protobuf.Reflection; 13 using NUnit.Framework; 14 using System.IO; 15 using Google.Protobuf.Buffers; 16 17 namespace Google.Protobuf 18 { 19 public class LegacyGeneratedCodeTest 20 { 21 [Test] IntermixingOfNewAndLegacyGeneratedCodeWorksWithCodedInputStream()22 public void IntermixingOfNewAndLegacyGeneratedCodeWorksWithCodedInputStream() 23 { 24 var message = new ParseContextEnabledMessageB 25 { 26 A = new LegacyGeneratedCodeMessageA 27 { 28 Bb = new ParseContextEnabledMessageB { OptionalInt32 = 12345 } 29 }, 30 OptionalInt32 = 6789 31 }; 32 var data = message.ToByteArray(); 33 34 // when parsing started using CodedInputStream and a message with legacy generated code 35 // is encountered somewhere in the parse tree, we still need to be able to use its 36 // MergeFrom(CodedInputStream) method to parse correctly. 37 var codedInput = new CodedInputStream(data); 38 var parsed = new ParseContextEnabledMessageB(); 39 codedInput.ReadRawMessage(parsed); 40 Assert.IsTrue(codedInput.IsAtEnd); 41 42 Assert.AreEqual(12345, parsed.A.Bb.OptionalInt32); 43 Assert.AreEqual(6789, parsed.OptionalInt32); 44 } 45 46 [Test] LegacyGeneratedCodeThrowsWithReadOnlySequence()47 public void LegacyGeneratedCodeThrowsWithReadOnlySequence() 48 { 49 var message = new ParseContextEnabledMessageB 50 { 51 A = new LegacyGeneratedCodeMessageA 52 { 53 Bb = new ParseContextEnabledMessageB { OptionalInt32 = 12345 } 54 }, 55 OptionalInt32 = 6789 56 }; 57 var data = message.ToByteArray(); 58 59 // if parsing started using ReadOnlySequence and we don't have a CodedInputStream 60 // instance at hand, we cannot fall back to the legacy MergeFrom(CodedInputStream) 61 // method and parsing will fail. As a consequence, one can only use parsing 62 // from ReadOnlySequence if all the messages in the parsing tree have their generated 63 // code up to date. 64 var exception = Assert.Throws<InvalidProtocolBufferException>(() => 65 { 66 ParseContext.Initialize(new ReadOnlySequence<byte>(data), out ParseContext parseCtx); 67 var parsed = new ParseContextEnabledMessageB(); 68 ParsingPrimitivesMessages.ReadRawMessage(ref parseCtx, parsed); 69 }); 70 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); 71 } 72 73 [Test] IntermixingOfNewAndLegacyGeneratedCodeWorksWithCodedOutputStream()74 public void IntermixingOfNewAndLegacyGeneratedCodeWorksWithCodedOutputStream() 75 { 76 // when serialization started using CodedOutputStream and a message with legacy generated code 77 // is encountered somewhere in the parse tree, we still need to be able to use its 78 // WriteTo(CodedOutputStream) method to serialize correctly. 79 var ms = new MemoryStream(); 80 var codedOutput = new CodedOutputStream(ms); 81 var message = new ParseContextEnabledMessageB 82 { 83 A = new LegacyGeneratedCodeMessageA 84 { 85 Bb = new ParseContextEnabledMessageB { OptionalInt32 = 12345 } 86 }, 87 OptionalInt32 = 6789 88 }; 89 message.WriteTo(codedOutput); 90 codedOutput.Flush(); 91 92 var codedInput = new CodedInputStream(ms.ToArray()); 93 var parsed = new ParseContextEnabledMessageB(); 94 codedInput.ReadRawMessage(parsed); 95 Assert.IsTrue(codedInput.IsAtEnd); 96 97 Assert.AreEqual(12345, parsed.A.Bb.OptionalInt32); 98 Assert.AreEqual(6789, parsed.OptionalInt32); 99 } 100 101 [Test] LegacyGeneratedCodeThrowsWithIBufferWriter()102 public void LegacyGeneratedCodeThrowsWithIBufferWriter() 103 { 104 // if serialization started using IBufferWriter and we don't have a CodedOutputStream 105 // instance at hand, we cannot fall back to the legacy WriteTo(CodedOutputStream) 106 // method and serializatin will fail. As a consequence, one can only use serialization 107 // to IBufferWriter if all the messages in the parsing tree have their generated 108 // code up to date. 109 var message = new ParseContextEnabledMessageB 110 { 111 A = new LegacyGeneratedCodeMessageA 112 { 113 Bb = new ParseContextEnabledMessageB { OptionalInt32 = 12345 } 114 }, 115 OptionalInt32 = 6789 116 }; 117 var exception = Assert.Throws<InvalidProtocolBufferException>(() => 118 { 119 WriteContext.Initialize(new TestArrayBufferWriter<byte>(), out WriteContext writeCtx); 120 ((IBufferMessage)message).InternalWriteTo(ref writeCtx); 121 }); 122 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); 123 } 124 125 // hand-modified version of a generated message that only provides the legacy 126 // MergeFrom(CodedInputStream) method and doesn't implement IBufferMessage. 127 private sealed partial class LegacyGeneratedCodeMessageA : pb::IMessage { 128 private pb::UnknownFieldSet _unknownFields; 129 130 pbr::MessageDescriptor pb::IMessage.Descriptor => throw new System.NotImplementedException(); 131 132 /// <summary>Field number for the "bb" field.</summary> 133 public const int BbFieldNumber = 1; 134 private ParseContextEnabledMessageB bb_; 135 public ParseContextEnabledMessageB Bb { 136 get { return bb_; } 137 set { 138 bb_ = value; 139 } 140 } 141 WriteTo(pb::CodedOutputStream output)142 public void WriteTo(pb::CodedOutputStream output) { 143 if (bb_ != null) { 144 output.WriteRawTag(10); 145 output.WriteMessage(Bb); 146 } 147 if (_unknownFields != null) { 148 _unknownFields.WriteTo(output); 149 } 150 } 151 CalculateSize()152 public int CalculateSize() { 153 int size = 0; 154 if (bb_ != null) { 155 size += 1 + pb::CodedOutputStream.ComputeMessageSize(Bb); 156 } 157 if (_unknownFields != null) { 158 size += _unknownFields.CalculateSize(); 159 } 160 return size; 161 } 162 MergeFrom(pb::CodedInputStream input)163 public void MergeFrom(pb::CodedInputStream input) { 164 uint tag; 165 while ((tag = input.ReadTag()) != 0) { 166 switch(tag) { 167 default: 168 _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); 169 break; 170 case 10: { 171 if (bb_ == null) { 172 Bb = new ParseContextEnabledMessageB(); 173 } 174 input.ReadMessage(Bb); 175 break; 176 } 177 } 178 } 179 } 180 } 181 182 // hand-modified version of a generated message that does provide 183 // the new InternalMergeFrom(ref ParseContext) method. 184 private sealed partial class ParseContextEnabledMessageB : pb::IBufferMessage { 185 private pb::UnknownFieldSet _unknownFields; 186 187 pbr::MessageDescriptor pb::IMessage.Descriptor => throw new System.NotImplementedException(); 188 189 /// <summary>Field number for the "a" field.</summary> 190 public const int AFieldNumber = 1; 191 private LegacyGeneratedCodeMessageA a_; 192 public LegacyGeneratedCodeMessageA A { 193 get { return a_; } 194 set { 195 a_ = value; 196 } 197 } 198 199 /// <summary>Field number for the "optional_int32" field.</summary> 200 public const int OptionalInt32FieldNumber = 2; 201 private int optionalInt32_; 202 public int OptionalInt32 { 203 get { return optionalInt32_; } 204 set { 205 optionalInt32_ = value; 206 } 207 } 208 WriteTo(pb::CodedOutputStream output)209 public void WriteTo(pb::CodedOutputStream output) { 210 output.WriteRawMessage(this); 211 } 212 IBufferMessage.InternalWriteTo(ref pb::WriteContext output)213 void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) 214 { 215 if (a_ != null) 216 { 217 output.WriteRawTag(10); 218 output.WriteMessage(A); 219 } 220 if (OptionalInt32 != 0) 221 { 222 output.WriteRawTag(16); 223 output.WriteInt32(OptionalInt32); 224 } 225 if (_unknownFields != null) 226 { 227 _unknownFields.WriteTo(ref output); 228 } 229 } 230 CalculateSize()231 public int CalculateSize() { 232 int size = 0; 233 if (a_ != null) { 234 size += 1 + pb::CodedOutputStream.ComputeMessageSize(A); 235 } 236 if (OptionalInt32 != 0) { 237 size += 1 + pb::CodedOutputStream.ComputeInt32Size(OptionalInt32); 238 } 239 if (_unknownFields != null) { 240 size += _unknownFields.CalculateSize(); 241 } 242 return size; 243 } MergeFrom(pb::CodedInputStream input)244 public void MergeFrom(pb::CodedInputStream input) { 245 input.ReadRawMessage(this); 246 } 247 IBufferMessage.InternalMergeFrom(ref pb::ParseContext input)248 void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { 249 uint tag; 250 while ((tag = input.ReadTag()) != 0) { 251 switch(tag) { 252 default: 253 _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); 254 break; 255 case 10: { 256 if (a_ == null) { 257 A = new LegacyGeneratedCodeMessageA(); 258 } 259 input.ReadMessage(A); 260 break; 261 } 262 case 16: { 263 OptionalInt32 = input.ReadInt32(); 264 break; 265 } 266 } 267 } 268 } 269 } 270 } 271 }