1#region Copyright notice and license
2// Protocol Buffers - Google's data interchange format
3// Copyright 2015 Google Inc.  All rights reserved.
4// https://developers.google.com/protocol-buffers/
5//
6// Redistribution and use in source and binary forms, with or without
7// modification, are permitted provided that the following conditions are
8// met:
9//
10//     * Redistributions of source code must retain the above copyright
11// notice, this list of conditions and the following disclaimer.
12//     * Redistributions in binary form must reproduce the above
13// copyright notice, this list of conditions and the following disclaimer
14// in the documentation and/or other materials provided with the
15// distribution.
16//     * Neither the name of Google Inc. nor the names of its
17// contributors may be used to endorse or promote products derived from
18// this software without specific prior written permission.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31#endregion
32
33using System.Collections.Generic;
34using System.IO;
35using System.Reflection;
36using Google.Protobuf.TestProtos;
37using NUnit.Framework;
38
39namespace Google.Protobuf
40{
41    public class FieldCodecTest
42    {
43#pragma warning disable 0414 // Used by tests via reflection - do not remove!
44        private static readonly List<ICodecTestData> Codecs = new List<ICodecTestData>
45        {
46            new FieldCodecTestData<bool>(FieldCodec.ForBool(100), true, "FixedBool"),
47            new FieldCodecTestData<string>(FieldCodec.ForString(100), "sample", "String"),
48            new FieldCodecTestData<ByteString>(FieldCodec.ForBytes(100), ByteString.CopyFrom(1, 2, 3), "Bytes"),
49            new FieldCodecTestData<int>(FieldCodec.ForInt32(100), -1000, "Int32"),
50            new FieldCodecTestData<int>(FieldCodec.ForSInt32(100), -1000, "SInt32"),
51            new FieldCodecTestData<int>(FieldCodec.ForSFixed32(100), -1000, "SFixed32"),
52            new FieldCodecTestData<uint>(FieldCodec.ForUInt32(100), 1234, "UInt32"),
53            new FieldCodecTestData<uint>(FieldCodec.ForFixed32(100), 1234, "Fixed32"),
54            new FieldCodecTestData<long>(FieldCodec.ForInt64(100), -1000, "Int64"),
55            new FieldCodecTestData<long>(FieldCodec.ForSInt64(100), -1000, "SInt64"),
56            new FieldCodecTestData<long>(FieldCodec.ForSFixed64(100), -1000, "SFixed64"),
57            new FieldCodecTestData<ulong>(FieldCodec.ForUInt64(100), 1234, "UInt64"),
58            new FieldCodecTestData<ulong>(FieldCodec.ForFixed64(100), 1234, "Fixed64"),
59            new FieldCodecTestData<float>(FieldCodec.ForFloat(100), 1234.5f, "FixedFloat"),
60            new FieldCodecTestData<double>(FieldCodec.ForDouble(100), 1234567890.5d, "FixedDouble"),
61            new FieldCodecTestData<ForeignEnum>(
62                FieldCodec.ForEnum(100, t => (int) t, t => (ForeignEnum) t), ForeignEnum.ForeignBaz, "Enum"),
63            new FieldCodecTestData<ForeignMessage>(
64                FieldCodec.ForMessage(100, ForeignMessage.Parser), new ForeignMessage { C = 10 }, "Message"),
65        };
66#pragma warning restore 0414
67
68        [Test, TestCaseSource("Codecs")]
69        public void RoundTripWithTag(ICodecTestData codec)
70        {
71            codec.TestRoundTripWithTag();
72        }
73
74        [Test, TestCaseSource("Codecs")]
75        public void RoundTripRaw(ICodecTestData codec)
76        {
77            codec.TestRoundTripRaw();
78        }
79
80        [Test, TestCaseSource("Codecs")]
81        public void CalculateSize(ICodecTestData codec)
82        {
83            codec.TestCalculateSizeWithTag();
84        }
85
86        [Test, TestCaseSource("Codecs")]
87        public void DefaultValue(ICodecTestData codec)
88        {
89            codec.TestDefaultValue();
90        }
91
92        [Test, TestCaseSource("Codecs")]
93        public void FixedSize(ICodecTestData codec)
94        {
95            codec.TestFixedSize();
96        }
97
98        // This is ugly, but it means we can have a non-generic interface.
99        // It feels like NUnit should support this better, but I don't know
100        // of any better ways right now.
101        public interface ICodecTestData
102        {
103            void TestRoundTripRaw();
104            void TestRoundTripWithTag();
105            void TestCalculateSizeWithTag();
106            void TestDefaultValue();
107            void TestFixedSize();
108        }
109
110        public class FieldCodecTestData<T> : ICodecTestData
111        {
112            private readonly FieldCodec<T> codec;
113            private readonly T sampleValue;
114            private readonly string name;
115
116            public FieldCodecTestData(FieldCodec<T> codec, T sampleValue, string name)
117            {
118                this.codec = codec;
119                this.sampleValue = sampleValue;
120                this.name = name;
121            }
122
123            public void TestRoundTripRaw()
124            {
125                var stream = new MemoryStream();
126                var codedOutput = new CodedOutputStream(stream);
127                WriteContext.Initialize(codedOutput, out WriteContext ctx);
128                try
129                {
130                    // only write the value using the codec
131                    codec.ValueWriter(ref ctx, sampleValue);
132                }
133                finally
134                {
135                    ctx.CopyStateTo(codedOutput);
136                }
137                codedOutput.Flush();
138                stream.Position = 0;
139                var codedInput = new CodedInputStream(stream);
140                Assert.AreEqual(sampleValue, codec.Read(codedInput));
141                Assert.IsTrue(codedInput.IsAtEnd);
142            }
143
144            public void TestRoundTripWithTag()
145            {
146                var stream = new MemoryStream();
147                var codedOutput = new CodedOutputStream(stream);
148                codec.WriteTagAndValue(codedOutput, sampleValue);
149                codedOutput.Flush();
150                stream.Position = 0;
151                var codedInput = new CodedInputStream(stream);
152                codedInput.AssertNextTag(codec.Tag);
153                Assert.AreEqual(sampleValue, codec.Read(codedInput));
154                Assert.IsTrue(codedInput.IsAtEnd);
155            }
156
157            public void TestCalculateSizeWithTag()
158            {
159                var stream = new MemoryStream();
160                var codedOutput = new CodedOutputStream(stream);
161                codec.WriteTagAndValue(codedOutput, sampleValue);
162                codedOutput.Flush();
163                Assert.AreEqual(stream.Position, codec.CalculateSizeWithTag(sampleValue));
164            }
165
166            public void TestDefaultValue()
167            {
168                // WriteTagAndValue ignores default values
169                var stream = new MemoryStream();
170                var codedOutput = new CodedOutputStream(stream);
171                codec.WriteTagAndValue(codedOutput, codec.DefaultValue);
172                codedOutput.Flush();
173                Assert.AreEqual(0, stream.Position);
174                Assert.AreEqual(0, codec.CalculateSizeWithTag(codec.DefaultValue));
175                if (typeof(T).GetTypeInfo().IsValueType)
176                {
177                    Assert.AreEqual(default(T), codec.DefaultValue);
178                }
179
180                // The plain ValueWriter/ValueReader delegates don't.
181                if (codec.DefaultValue != null) // This part isn't appropriate for message types.
182                {
183                    codedOutput = new CodedOutputStream(stream);
184                    WriteContext.Initialize(codedOutput, out WriteContext ctx);
185                    try
186                    {
187                        // only write the value using the codec
188                        codec.ValueWriter(ref ctx, codec.DefaultValue);
189                    }
190                    finally
191                    {
192                        ctx.CopyStateTo(codedOutput);
193                    }
194                    codedOutput.Flush();
195                    Assert.AreNotEqual(0, stream.Position);
196                    Assert.AreEqual(stream.Position, codec.ValueSizeCalculator(codec.DefaultValue));
197                    stream.Position = 0;
198                    var codedInput = new CodedInputStream(stream);
199                    Assert.AreEqual(codec.DefaultValue, codec.Read(codedInput));
200                }
201            }
202
203            public void TestFixedSize()
204            {
205                Assert.AreEqual(name.Contains("Fixed"), codec.FixedSize != 0);
206            }
207
208            public override string ToString()
209            {
210                return name;
211            }
212        }
213    }
214}
215