From 638a0813b40ff8ed8f2d082053e2ac1ab0e07b15 Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Tue, 14 Apr 2020 18:04:47 +0200 Subject: [PATCH] increase coverage of GeneratedMessageTest --- .../GeneratedMessageTest.cs | 142 ++++++++++++------ .../MessageParsingHelpers.cs | 86 +++++++++++ .../ReadOnlySequenceFactory.cs | 128 ++++++++++++++++ 3 files changed, 312 insertions(+), 44 deletions(-) create mode 100644 csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs create mode 100644 csharp/src/Google.Protobuf.Test/ReadOnlySequenceFactory.cs diff --git a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs index 103df7dd2f..3499e6617d 100644 --- a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs +++ b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs @@ -131,8 +131,8 @@ namespace Google.Protobuf // Without setting any values, there's nothing to write. byte[] bytes = message.ToByteArray(); Assert.AreEqual(0, bytes.Length); - TestAllTypes parsed = TestAllTypes.Parser.ParseFrom(bytes); - Assert.AreEqual(message, parsed); + + MessageParsingHelpers.AssertRoundtrip(TestAllTypes.Parser, message); } [Test] @@ -165,8 +165,8 @@ namespace Google.Protobuf }; byte[] bytes = message.ToByteArray(); - TestAllTypes parsed = TestAllTypes.Parser.ParseFrom(bytes); - Assert.AreEqual(message, parsed); + + MessageParsingHelpers.AssertRoundtrip(TestAllTypes.Parser, message); } [Test] @@ -199,8 +199,8 @@ namespace Google.Protobuf }; byte[] bytes = message.ToByteArray(); - TestAllTypes parsed = TestAllTypes.Parser.ParseFrom(bytes); - Assert.AreEqual(message, parsed); + + MessageParsingHelpers.AssertRoundtrip(TestAllTypes.Parser, message); } // Note that not every map within map_unittest_proto3 is used. They all go through very @@ -231,8 +231,8 @@ namespace Google.Protobuf }; byte[] bytes = message.ToByteArray(); - TestMap parsed = TestMap.Parser.ParseFrom(bytes); - Assert.AreEqual(message, parsed); + + MessageParsingHelpers.AssertRoundtrip(TestMap.Parser, message); } [Test] @@ -246,9 +246,14 @@ namespace Google.Protobuf byte[] bytes = message.ToByteArray(); Assert.AreEqual(2, bytes.Length); // Tag for field entry (1 byte), length of entry (0; 1 byte) - var parsed = TestMap.Parser.ParseFrom(bytes); - Assert.AreEqual(1, parsed.MapInt32Bytes.Count); - Assert.AreEqual(ByteString.Empty, parsed.MapInt32Bytes[0]); + MessageParsingHelpers.AssertReadingMessage( + TestMap.Parser, + bytes, + parsed=> + { + Assert.AreEqual(1, parsed.MapInt32Bytes.Count); + Assert.AreEqual(ByteString.Empty, parsed.MapInt32Bytes[0]); + }); } [Test] @@ -265,8 +270,13 @@ namespace Google.Protobuf output.WriteMessage(nestedMessage); output.Flush(); - var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray()); - Assert.AreEqual(nestedMessage, parsed.MapInt32ForeignMessage[0]); + MessageParsingHelpers.AssertReadingMessage( + TestMap.Parser, + memoryStream.ToArray(), + parsed => + { + Assert.AreEqual(nestedMessage, parsed.MapInt32ForeignMessage[0]); + }); } [Test] @@ -282,8 +292,13 @@ namespace Google.Protobuf output.WriteInt32(key); output.Flush(); - var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray()); - Assert.AreEqual(0.0, parsed.MapInt32Double[key]); + MessageParsingHelpers.AssertReadingMessage( + TestMap.Parser, + memoryStream.ToArray(), + parsed => + { + Assert.AreEqual(0.0, parsed.MapInt32Double[key]); + }); } [Test] @@ -299,8 +314,13 @@ namespace Google.Protobuf output.WriteInt32(key); output.Flush(); - var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray()); - Assert.AreEqual(new ForeignMessage(), parsed.MapInt32ForeignMessage[key]); + MessageParsingHelpers.AssertReadingMessage( + TestMap.Parser, + memoryStream.ToArray(), + parsed => + { + Assert.AreEqual(new ForeignMessage(), parsed.MapInt32ForeignMessage[key]); + }); } [Test] @@ -327,8 +347,13 @@ namespace Google.Protobuf output.WriteInt32(extra); output.Flush(); - var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray()); - Assert.AreEqual(value, parsed.MapInt32Int32[key]); + MessageParsingHelpers.AssertReadingMessage( + TestMap.Parser, + memoryStream.ToArray(), + parsed => + { + Assert.AreEqual(value, parsed.MapInt32Int32[key]); + }); } [Test] @@ -351,8 +376,13 @@ namespace Google.Protobuf output.WriteInt32(key); output.Flush(); - var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray()); - Assert.AreEqual(value, parsed.MapInt32Int32[key]); + MessageParsingHelpers.AssertReadingMessage( + TestMap.Parser, + memoryStream.ToArray(), + parsed => + { + Assert.AreEqual(value, parsed.MapInt32Int32[key]); + }); } [Test] @@ -397,13 +427,19 @@ namespace Google.Protobuf output.WriteInt32(value3); output.Flush(); - var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray()); - var expected = new TestMap - { - MapInt32Int32 = { { key1, value1 }, { key3, value3 } }, - MapStringString = { { key2, value2 } } - }; - Assert.AreEqual(expected, parsed); + + MessageParsingHelpers.AssertReadingMessage( + TestMap.Parser, + memoryStream.ToArray(), + parsed => + { + var expected = new TestMap + { + MapInt32Int32 = { { key1, value1 }, { key3, value3 } }, + MapStringString = { { key2, value2 } } + }; + Assert.AreEqual(expected, parsed); + }); } [Test] @@ -433,8 +469,13 @@ namespace Google.Protobuf output.WriteInt32(value2); output.Flush(); - var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray()); - Assert.AreEqual(value2, parsed.MapInt32Int32[key]); + MessageParsingHelpers.AssertReadingMessage( + TestMap.Parser, + memoryStream.ToArray(), + parsed => + { + Assert.AreEqual(value2, parsed.MapInt32Int32[key]); + }); } [Test] @@ -619,9 +660,10 @@ namespace Google.Protobuf var bytes = message.ToByteArray(); Assert.AreEqual(3, bytes.Length); // 2 bytes for the tag + 1 for the value - no string! - var message2 = TestAllTypes.Parser.ParseFrom(bytes); - Assert.AreEqual(message, message2); - Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofUint32, message2.OneofFieldCase); + MessageParsingHelpers.AssertRoundtrip(TestAllTypes.Parser, message, parsedMessage => + { + Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofUint32, parsedMessage.OneofFieldCase); + }); } [Test] @@ -633,9 +675,10 @@ namespace Google.Protobuf var bytes = message.ToByteArray(); Assert.AreEqual(3, bytes.Length); // 2 bytes for the tag + 1 for the value - it's still serialized - var message2 = TestAllTypes.Parser.ParseFrom(bytes); - Assert.AreEqual(message, message2); - Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofUint32, message2.OneofFieldCase); + MessageParsingHelpers.AssertRoundtrip(TestAllTypes.Parser, message, parsedMessage => + { + Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofUint32, parsedMessage.OneofFieldCase); + }); } [Test] @@ -651,10 +694,14 @@ namespace Google.Protobuf message.WriteTo(output); output.Flush(); - stream.Position = 0; - var parsed = TestAllTypes.Parser.ParseFrom(stream); - // TODO(jieluo): Add test back when DiscardUnknownFields API is supported. - // Assert.AreEqual(message, parsed); + MessageParsingHelpers.AssertReadingMessage( + TestAllTypes.Parser, + stream.ToArray(), + parsed => + { + // TODO(jieluo): Add test back when DiscardUnknownFields API is supported. + // Assert.AreEqual(message, parsed); + }); } [Test] @@ -663,8 +710,15 @@ namespace Google.Protobuf // Simple way of ensuring we can skip all kinds of fields. var data = SampleMessages.CreateFullTestAllTypes().ToByteArray(); var empty = Empty.Parser.ParseFrom(data); - // TODO(jieluo): Add test back when DiscardUnknownFields API is supported. - // Assert.AreNotEqual(new Empty(), empty); + + MessageParsingHelpers.AssertReadingMessage( + Empty.Parser, + data, + parsed => + { + // TODO(jieluo): Add test back when DiscardUnknownFields API is supported. + // Assert.AreNotEqual(new Empty(), empty); + }); } // This was originally seen as a conformance test failure. @@ -674,7 +728,7 @@ namespace Google.Protobuf // 130, 3 is the message tag // 1 is the data length - but there's no data. var data = new byte[] { 130, 3, 1 }; - Assert.Throws(() => TestAllTypes.Parser.ParseFrom(data)); + MessageParsingHelpers.AssertReadingMessageThrows(TestAllTypes.Parser, data); } /// @@ -695,7 +749,7 @@ namespace Google.Protobuf output.Flush(); stream.Position = 0; - Assert.Throws(() => TestAllTypes.Parser.ParseFrom(stream)); + MessageParsingHelpers.AssertReadingMessageThrows(TestAllTypes.Parser, stream.ToArray()); } [Test] diff --git a/csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs b/csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs new file mode 100644 index 0000000000..d629d9cef0 --- /dev/null +++ b/csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs @@ -0,0 +1,86 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 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 NUnit.Framework; +using System; +using Google.Protobuf.Buffers; +using System.Buffers; + +namespace Google.Protobuf +{ + public static class MessageParsingHelpers + { + public static void AssertReadingMessage(MessageParser parser, byte[] bytes, Action assert) where T : IMessage + { + var parsedStream = parser.ParseFrom(bytes); + + // Load content as single segment + var parsedBuffer = parser.ParseFrom(new ReadOnlySequence(bytes)); + assert(parsedBuffer); + + // Load content as multiple segments + parsedBuffer = parser.ParseFrom(ReadOnlySequenceFactory.CreateWithContent(bytes)); + assert(parsedBuffer); + + assert(parsedStream); + } + + public static void AssertReadingMessageThrows(MessageParser parser, byte[] bytes) + where TMessage : IMessage + where TException : Exception + { + Assert.Throws(() => parser.ParseFrom(bytes)); + + Assert.Throws(() => parser.ParseFrom(new ReadOnlySequence(bytes))); + } + + public static void AssertRoundtrip(MessageParser parser, T message, Action additionalAssert = null) where T : IMessage + { + var bytes = message.ToByteArray(); + + // Load content as single segment + var parsedBuffer = parser.ParseFrom(new ReadOnlySequence(bytes)); + Assert.AreEqual(message, parsedBuffer); + additionalAssert?.Invoke(parsedBuffer); + + // Load content as multiple segments + parsedBuffer = parser.ParseFrom(ReadOnlySequenceFactory.CreateWithContent(bytes)); + Assert.AreEqual(message, parsedBuffer); + additionalAssert?.Invoke(parsedBuffer); + + var parsedStream = parser.ParseFrom(bytes); + + Assert.AreEqual(message, parsedStream); + additionalAssert?.Invoke(parsedStream); + } + } +} \ No newline at end of file diff --git a/csharp/src/Google.Protobuf.Test/ReadOnlySequenceFactory.cs b/csharp/src/Google.Protobuf.Test/ReadOnlySequenceFactory.cs new file mode 100644 index 0000000000..f35844c72f --- /dev/null +++ b/csharp/src/Google.Protobuf.Test/ReadOnlySequenceFactory.cs @@ -0,0 +1,128 @@ +#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; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Google.Protobuf.Buffers +{ + internal static class ReadOnlySequenceFactory + { + public static ReadOnlySequence CreateWithContent(byte[] data, int segmentSize = 1) + { + var segments = new List(); + + segments.Add(new byte[0]); + var currentIndex = 0; + while (currentIndex < data.Length) + { + var segment = new List(); + for (; currentIndex < Math.Min(currentIndex + segmentSize, data.Length); currentIndex++) + { + segment.Add(data[currentIndex]); + } + segments.Add(segment.ToArray()); + segments.Add(new byte[0]); + } + + return CreateSegments(segments.ToArray()); + } + + /// + /// Originally from corefx, and has been contributed to Protobuf + /// https://github.com/dotnet/corefx/blob/e99ec129cfd594d53f4390bf97d1d736cff6f860/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceFactory.byte.cs + /// + private static ReadOnlySequence CreateSegments(params byte[][] inputs) + { + if (inputs == null || inputs.Length == 0) + { + throw new InvalidOperationException(); + } + + int i = 0; + + BufferSegment last = null; + BufferSegment first = null; + + do + { + byte[] s = inputs[i]; + int length = s.Length; + int dataOffset = length; + var chars = new byte[length * 2]; + + for (int j = 0; j < length; j++) + { + chars[dataOffset + j] = s[j]; + } + + // Create a segment that has offset relative to the OwnedMemory and OwnedMemory itself has offset relative to array + var memory = new Memory(chars).Slice(length, length); + + if (first == null) + { + first = new BufferSegment(memory); + last = first; + } + else + { + last = last.Append(memory); + } + i++; + } while (i < inputs.Length); + + return new ReadOnlySequence(first, 0, last, last.Memory.Length); + } + + private class BufferSegment : ReadOnlySequenceSegment + { + public BufferSegment(Memory memory) + { + Memory = memory; + } + + public BufferSegment Append(Memory memory) + { + var segment = new BufferSegment(memory) + { + RunningIndex = RunningIndex + Memory.Length + }; + Next = segment; + return segment; + } + } + } +} \ No newline at end of file