aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Jie Luo <anandolee@gmail.com>2017-02-13 10:54:34 -0800
committerGravatar GitHub <noreply@github.com>2017-02-13 10:54:34 -0800
commit3f6f73b796988f47bfc157d3a9faee81fad7fa89 (patch)
tree5011cf92db4abcf29d6d2e634307b04bf58935d9
parented423c2c521862df9c9bb4a6b557ef0c503807cb (diff)
parent7288689d8c1f32d1c0f14d33a98d41ebbe269c1e (diff)
Merge pull request #2701 from anandolee/master
Add csharp compatibility tests against v3.0.0
-rw-r--r--csharp/compatibility_tests/v3.0.0/protos/csharp/protos/unittest_issues.proto126
-rw-r--r--csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/map_unittest_proto3.proto120
-rw-r--r--csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_import_proto3.proto68
-rw-r--r--csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_import_public_proto3.proto42
-rw-r--r--csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_proto3.proto388
-rw-r--r--csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_well_known_types.proto114
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/ByteStringTest.cs171
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/CodedInputStreamExtensions.cs53
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/CodedInputStreamTest.cs598
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/CodedOutputStreamTest.cs419
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Collections/MapFieldTest.cs532
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs746
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Compatibility/PropertyInfoExtensionsTest.cs98
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Compatibility/TypeExtensionsTest.cs117
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/DeprecatedMemberTest.cs55
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/EqualityTester.cs64
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/FieldCodecTest.cs196
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/GeneratedMessageTest.cs723
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Google.Protobuf.Test.xproj19
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/IssuesTest.cs82
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/JsonParserTest.cs939
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/JsonTokenizerTest.cs408
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs259
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs218
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs94
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/SampleEnum.cs42
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/SampleMessages.cs99
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/TestCornerCases.cs62
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/TestProtos/ForeignMessagePartial.cs45
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs116
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/DurationTest.cs132
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/FieldMaskTest.cs62
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/TimestampTest.cs115
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs421
-rw-r--r--csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/project.json44
-rwxr-xr-xcsharp/compatibility_tests/v3.0.0/test.sh102
-rwxr-xr-xtests.sh5
37 files changed, 7893 insertions, 1 deletions
diff --git a/csharp/compatibility_tests/v3.0.0/protos/csharp/protos/unittest_issues.proto b/csharp/compatibility_tests/v3.0.0/protos/csharp/protos/unittest_issues.proto
new file mode 100644
index 00000000..6c9f7634
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/protos/csharp/protos/unittest_issues.proto
@@ -0,0 +1,126 @@
+syntax = "proto3";
+
+// These proto descriptors have at one time been reported as an issue or defect.
+// They are kept here to replicate the issue, and continue to verify the fix.
+
+// Issue: Non-"Google.Protobuffers" namespace will ensure that protobuffer library types are qualified
+option csharp_namespace = "UnitTest.Issues.TestProtos";
+
+package unittest_issues;
+option optimize_for = SPEED;
+
+// Issue 307: when generating doubly-nested types, any references
+// should be of the form A.Types.B.Types.C.
+message Issue307 {
+ message NestedOnce {
+ message NestedTwice {
+ }
+ }
+}
+
+// Old issue 13: http://code.google.com/p/protobuf-csharp-port/issues/detail?id=13
+// New issue 309: https://github.com/google/protobuf/issues/309
+
+// message A {
+// optional int32 _A = 1;
+// }
+
+// message B {
+// optional int32 B_ = 1;
+// }
+
+//message AB {
+// optional int32 a_b = 1;
+//}
+
+// Similar issue with numeric names
+// Java code failed too, so probably best for this to be a restriction.
+// See https://github.com/google/protobuf/issues/308
+// message NumberField {
+// optional int32 _01 = 1;
+// }
+
+// issue 19 - negative enum values
+
+enum NegativeEnum {
+ NEGATIVE_ENUM_ZERO = 0;
+ FiveBelow = -5;
+ MinusOne = -1;
+}
+
+message NegativeEnumMessage {
+ NegativeEnum value = 1;
+ repeated NegativeEnum values = 2 [packed = false];
+ repeated NegativeEnum packed_values = 3 [packed=true];
+}
+
+// Issue 21: http://code.google.com/p/protobuf-csharp-port/issues/detail?id=21
+// Decorate fields with [deprecated=true] as [System.Obsolete]
+
+message DeprecatedChild {
+}
+
+enum DeprecatedEnum {
+ DEPRECATED_ZERO = 0;
+ one = 1;
+}
+
+message DeprecatedFieldsMessage {
+ int32 PrimitiveValue = 1 [deprecated = true];
+ repeated int32 PrimitiveArray = 2 [deprecated = true];
+
+ DeprecatedChild MessageValue = 3 [deprecated = true];
+ repeated DeprecatedChild MessageArray = 4 [deprecated = true];
+
+ DeprecatedEnum EnumValue = 5 [deprecated = true];
+ repeated DeprecatedEnum EnumArray = 6 [deprecated = true];
+}
+
+// Issue 45: http://code.google.com/p/protobuf-csharp-port/issues/detail?id=45
+message ItemField {
+ int32 item = 1;
+}
+
+message ReservedNames {
+ // Force a nested type called Types
+ message SomeNestedType {
+ }
+
+ int32 types = 1;
+ int32 descriptor = 2;
+}
+
+message TestJsonFieldOrdering {
+ // These fields are deliberately not declared in numeric
+ // order, and the oneof fields aren't contiguous either.
+ // This allows for reasonably robust tests of JSON output
+ // ordering.
+ // TestFieldOrderings in unittest_proto3.proto is similar,
+ // but doesn't include oneofs.
+ // TODO: Consider adding oneofs to TestFieldOrderings, although
+ // that will require fixing other tests in multiple platforms.
+ // Alternatively, consider just adding this to
+ // unittest_proto3.proto if multiple platforms want it.
+
+ int32 plain_int32 = 4;
+
+ oneof o1 {
+ string o1_string = 2;
+ int32 o1_int32 = 5;
+ }
+
+ string plain_string = 1;
+
+ oneof o2 {
+ int32 o2_int32 = 6;
+ string o2_string = 3;
+ }
+
+}
+
+message TestJsonName {
+ // Message for testing the effects for of the json_name option
+ string name = 1;
+ string description = 2 [json_name = "desc"];
+ string guid = 3 [json_name = "exid"];
+}
diff --git a/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/map_unittest_proto3.proto b/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/map_unittest_proto3.proto
new file mode 100644
index 00000000..16be2773
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/map_unittest_proto3.proto
@@ -0,0 +1,120 @@
+// 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.
+
+// This file is mostly equivalent to map_unittest.proto, but imports
+// unittest_proto3.proto instead of unittest.proto, so that it only
+// uses proto3 messages. This makes it suitable for testing
+// implementations which only support proto3.
+// The TestRequiredMessageMap message has been removed as there are no
+// required fields in proto3.
+syntax = "proto3";
+
+option cc_enable_arenas = true;
+option csharp_namespace = "Google.Protobuf.TestProtos";
+
+import "google/protobuf/unittest_proto3.proto";
+
+// We don't put this in a package within proto2 because we need to make sure
+// that the generated code doesn't depend on being in the proto2 namespace.
+// In map_test_util.h we do "using namespace unittest = protobuf_unittest".
+package protobuf_unittest;
+
+// Tests maps.
+message TestMap {
+ map<int32 , int32 > map_int32_int32 = 1;
+ map<int64 , int64 > map_int64_int64 = 2;
+ map<uint32 , uint32 > map_uint32_uint32 = 3;
+ map<uint64 , uint64 > map_uint64_uint64 = 4;
+ map<sint32 , sint32 > map_sint32_sint32 = 5;
+ map<sint64 , sint64 > map_sint64_sint64 = 6;
+ map<fixed32 , fixed32 > map_fixed32_fixed32 = 7;
+ map<fixed64 , fixed64 > map_fixed64_fixed64 = 8;
+ map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 9;
+ map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 10;
+ map<int32 , float > map_int32_float = 11;
+ map<int32 , double > map_int32_double = 12;
+ map<bool , bool > map_bool_bool = 13;
+ map<string , string > map_string_string = 14;
+ map<int32 , bytes > map_int32_bytes = 15;
+ map<int32 , MapEnum > map_int32_enum = 16;
+ map<int32 , ForeignMessage> map_int32_foreign_message = 17;
+}
+
+message TestMapSubmessage {
+ TestMap test_map = 1;
+}
+
+message TestMessageMap {
+ map<int32, TestAllTypes> map_int32_message = 1;
+}
+
+// Two map fields share the same entry default instance.
+message TestSameTypeMap {
+ map<int32, int32> map1 = 1;
+ map<int32, int32> map2 = 2;
+}
+
+enum MapEnum {
+ MAP_ENUM_FOO = 0;
+ MAP_ENUM_BAR = 1;
+ MAP_ENUM_BAZ = 2;
+}
+
+message TestArenaMap {
+ map<int32 , int32 > map_int32_int32 = 1;
+ map<int64 , int64 > map_int64_int64 = 2;
+ map<uint32 , uint32 > map_uint32_uint32 = 3;
+ map<uint64 , uint64 > map_uint64_uint64 = 4;
+ map<sint32 , sint32 > map_sint32_sint32 = 5;
+ map<sint64 , sint64 > map_sint64_sint64 = 6;
+ map<fixed32 , fixed32 > map_fixed32_fixed32 = 7;
+ map<fixed64 , fixed64 > map_fixed64_fixed64 = 8;
+ map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 9;
+ map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 10;
+ map<int32 , float > map_int32_float = 11;
+ map<int32 , double > map_int32_double = 12;
+ map<bool , bool > map_bool_bool = 13;
+ map<int32 , MapEnum > map_int32_enum = 14;
+ map<int32 , ForeignMessage> map_int32_foreign_message = 15;
+}
+
+// Previously, message containing enum called Type cannot be used as value of
+// map field.
+message MessageContainingEnumCalledType {
+ enum Type {
+ TYPE_FOO = 0;
+ }
+ map<int32, MessageContainingEnumCalledType> type = 1;
+}
+
+// Previously, message cannot contain map field called "entry".
+message MessageContainingMapCalledEntry {
+ map<int32, int32> entry = 1;
+}
diff --git a/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_import_proto3.proto b/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_import_proto3.proto
new file mode 100644
index 00000000..59673eaf
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_import_proto3.proto
@@ -0,0 +1,68 @@
+// 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.
+
+// Author: kenton@google.com (Kenton Varda)
+// Based on original Protocol Buffers design by
+// Sanjay Ghemawat, Jeff Dean, and others.
+//
+// A proto file which is imported by unittest_proto3.proto to test importing.
+
+syntax = "proto3";
+
+// We don't put this in a package within proto2 because we need to make sure
+// that the generated code doesn't depend on being in the proto2 namespace.
+// In test_util.h we do
+// "using namespace unittest_import = protobuf_unittest_import".
+package protobuf_unittest_import;
+
+option optimize_for = SPEED;
+option cc_enable_arenas = true;
+
+// Exercise the java_package option.
+option java_package = "com.google.protobuf.test";
+option csharp_namespace = "Google.Protobuf.TestProtos";
+
+// Do not set a java_outer_classname here to verify that Proto2 works without
+// one.
+
+// Test public import
+import public "google/protobuf/unittest_import_public_proto3.proto";
+
+message ImportMessage {
+ int32 d = 1;
+}
+
+enum ImportEnum {
+ IMPORT_ENUM_UNSPECIFIED = 0;
+ IMPORT_FOO = 7;
+ IMPORT_BAR = 8;
+ IMPORT_BAZ = 9;
+}
+
diff --git a/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_import_public_proto3.proto b/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_import_public_proto3.proto
new file mode 100644
index 00000000..d6f11e28
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_import_public_proto3.proto
@@ -0,0 +1,42 @@
+// 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.
+
+// Author: liujisi@google.com (Pherl Liu)
+
+syntax = "proto3";
+
+package protobuf_unittest_import;
+
+option java_package = "com.google.protobuf.test";
+option csharp_namespace = "Google.Protobuf.TestProtos";
+
+message PublicImportMessage {
+ int32 e = 1;
+}
diff --git a/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_proto3.proto b/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_proto3.proto
new file mode 100644
index 00000000..f59d2178
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_proto3.proto
@@ -0,0 +1,388 @@
+// 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.
+
+// Author: kenton@google.com (Kenton Varda)
+// Based on original Protocol Buffers design by
+// Sanjay Ghemawat, Jeff Dean, and others.
+//
+// A proto file we will use for unit testing.
+
+syntax = "proto3";
+
+// Some generic_services option(s) added automatically.
+// See: http://go/proto2-generic-services-default
+option cc_generic_services = true; // auto-added
+option java_generic_services = true; // auto-added
+option py_generic_services = true; // auto-added
+option cc_enable_arenas = true;
+option csharp_namespace = "Google.Protobuf.TestProtos";
+
+import "google/protobuf/unittest_import_proto3.proto";
+
+// We don't put this in a package within proto2 because we need to make sure
+// that the generated code doesn't depend on being in the proto2 namespace.
+// In test_util.h we do "using namespace unittest = protobuf_unittest".
+package protobuf_unittest;
+
+// Protos optimized for SPEED use a strict superset of the generated code
+// of equivalent ones optimized for CODE_SIZE, so we should optimize all our
+// tests for speed unless explicitly testing code size optimization.
+option optimize_for = SPEED;
+
+option java_outer_classname = "UnittestProto";
+
+// This proto includes every type of field in both singular and repeated
+// forms.
+message TestAllTypes {
+ message NestedMessage {
+ // The field name "b" fails to compile in proto1 because it conflicts with
+ // a local variable named "b" in one of the generated methods. Doh.
+ // This file needs to compile in proto1 to test backwards-compatibility.
+ int32 bb = 1;
+ }
+
+ enum NestedEnum {
+ NESTED_ENUM_UNSPECIFIED = 0;
+ FOO = 1;
+ BAR = 2;
+ BAZ = 3;
+ NEG = -1; // Intentionally negative.
+ }
+
+ // Singular
+ int32 single_int32 = 1;
+ int64 single_int64 = 2;
+ uint32 single_uint32 = 3;
+ uint64 single_uint64 = 4;
+ sint32 single_sint32 = 5;
+ sint64 single_sint64 = 6;
+ fixed32 single_fixed32 = 7;
+ fixed64 single_fixed64 = 8;
+ sfixed32 single_sfixed32 = 9;
+ sfixed64 single_sfixed64 = 10;
+ float single_float = 11;
+ double single_double = 12;
+ bool single_bool = 13;
+ string single_string = 14;
+ bytes single_bytes = 15;
+
+ NestedMessage single_nested_message = 18;
+ ForeignMessage single_foreign_message = 19;
+ protobuf_unittest_import.ImportMessage single_import_message = 20;
+
+ NestedEnum single_nested_enum = 21;
+ ForeignEnum single_foreign_enum = 22;
+ protobuf_unittest_import.ImportEnum single_import_enum = 23;
+
+ // Defined in unittest_import_public.proto
+ protobuf_unittest_import.PublicImportMessage
+ single_public_import_message = 26;
+
+ // Repeated
+ repeated int32 repeated_int32 = 31;
+ repeated int64 repeated_int64 = 32;
+ repeated uint32 repeated_uint32 = 33;
+ repeated uint64 repeated_uint64 = 34;
+ repeated sint32 repeated_sint32 = 35;
+ repeated sint64 repeated_sint64 = 36;
+ repeated fixed32 repeated_fixed32 = 37;
+ repeated fixed64 repeated_fixed64 = 38;
+ repeated sfixed32 repeated_sfixed32 = 39;
+ repeated sfixed64 repeated_sfixed64 = 40;
+ repeated float repeated_float = 41;
+ repeated double repeated_double = 42;
+ repeated bool repeated_bool = 43;
+ repeated string repeated_string = 44;
+ repeated bytes repeated_bytes = 45;
+
+ repeated NestedMessage repeated_nested_message = 48;
+ repeated ForeignMessage repeated_foreign_message = 49;
+ repeated protobuf_unittest_import.ImportMessage repeated_import_message = 50;
+
+ repeated NestedEnum repeated_nested_enum = 51;
+ repeated ForeignEnum repeated_foreign_enum = 52;
+ repeated protobuf_unittest_import.ImportEnum repeated_import_enum = 53;
+ // Defined in unittest_import_public.proto
+ repeated protobuf_unittest_import.PublicImportMessage
+ repeated_public_import_message = 54;
+
+ // For oneof test
+ oneof oneof_field {
+ uint32 oneof_uint32 = 111;
+ NestedMessage oneof_nested_message = 112;
+ string oneof_string = 113;
+ bytes oneof_bytes = 114;
+ }
+}
+
+// This proto includes a recusively nested message.
+message NestedTestAllTypes {
+ NestedTestAllTypes child = 1;
+ TestAllTypes payload = 2;
+ repeated NestedTestAllTypes repeated_child = 3;
+}
+
+message TestDeprecatedFields {
+ int32 deprecated_int32 = 1 [deprecated=true];
+}
+
+// Define these after TestAllTypes to make sure the compiler can handle
+// that.
+message ForeignMessage {
+ int32 c = 1;
+}
+
+enum ForeignEnum {
+ FOREIGN_UNSPECIFIED = 0;
+ FOREIGN_FOO = 4;
+ FOREIGN_BAR = 5;
+ FOREIGN_BAZ = 6;
+}
+
+message TestReservedFields {
+ reserved 2, 15, 9 to 11;
+ reserved "bar", "baz";
+}
+
+
+// Test that we can use NestedMessage from outside TestAllTypes.
+message TestForeignNested {
+ TestAllTypes.NestedMessage foreign_nested = 1;
+}
+
+// Test that really large tag numbers don't break anything.
+message TestReallyLargeTagNumber {
+ // The largest possible tag number is 2^28 - 1, since the wire format uses
+ // three bits to communicate wire type.
+ int32 a = 1;
+ int32 bb = 268435455;
+}
+
+message TestRecursiveMessage {
+ TestRecursiveMessage a = 1;
+ int32 i = 2;
+}
+
+// Test that mutual recursion works.
+message TestMutualRecursionA {
+ TestMutualRecursionB bb = 1;
+}
+
+message TestMutualRecursionB {
+ TestMutualRecursionA a = 1;
+ int32 optional_int32 = 2;
+}
+
+
+// Test an enum that has multiple values with the same number.
+enum TestEnumWithDupValue {
+ TEST_ENUM_WITH_DUP_VALUE_UNSPECIFIED = 0;
+ option allow_alias = true;
+
+ FOO1 = 1;
+ BAR1 = 2;
+ BAZ = 3;
+ FOO2 = 1;
+ BAR2 = 2;
+}
+
+// Test an enum with large, unordered values.
+enum TestSparseEnum {
+ TEST_SPARSE_ENUM_UNSPECIFIED = 0;
+ SPARSE_A = 123;
+ SPARSE_B = 62374;
+ SPARSE_C = 12589234;
+ SPARSE_D = -15;
+ SPARSE_E = -53452;
+ // In proto3, value 0 must be the first one specified
+ // SPARSE_F = 0;
+ SPARSE_G = 2;
+}
+
+// Test message with CamelCase field names. This violates Protocol Buffer
+// standard style.
+message TestCamelCaseFieldNames {
+ int32 PrimitiveField = 1;
+ string StringField = 2;
+ ForeignEnum EnumField = 3;
+ ForeignMessage MessageField = 4;
+
+ repeated int32 RepeatedPrimitiveField = 7;
+ repeated string RepeatedStringField = 8;
+ repeated ForeignEnum RepeatedEnumField = 9;
+ repeated ForeignMessage RepeatedMessageField = 10;
+}
+
+
+// We list fields out of order, to ensure that we're using field number and not
+// field index to determine serialization order.
+message TestFieldOrderings {
+ string my_string = 11;
+ int64 my_int = 1;
+ float my_float = 101;
+ message NestedMessage {
+ int64 oo = 2;
+ // The field name "b" fails to compile in proto1 because it conflicts with
+ // a local variable named "b" in one of the generated methods. Doh.
+ // This file needs to compile in proto1 to test backwards-compatibility.
+ int32 bb = 1;
+ }
+
+ NestedMessage single_nested_message = 200;
+}
+
+message SparseEnumMessage {
+ TestSparseEnum sparse_enum = 1;
+}
+
+// Test String and Bytes: string is for valid UTF-8 strings
+message OneString {
+ string data = 1;
+}
+
+message MoreString {
+ repeated string data = 1;
+}
+
+message OneBytes {
+ bytes data = 1;
+}
+
+message MoreBytes {
+ bytes data = 1;
+}
+
+// Test int32, uint32, int64, uint64, and bool are all compatible
+message Int32Message {
+ int32 data = 1;
+}
+
+message Uint32Message {
+ uint32 data = 1;
+}
+
+message Int64Message {
+ int64 data = 1;
+}
+
+message Uint64Message {
+ uint64 data = 1;
+}
+
+message BoolMessage {
+ bool data = 1;
+}
+
+// Test oneofs.
+message TestOneof {
+ oneof foo {
+ int32 foo_int = 1;
+ string foo_string = 2;
+ TestAllTypes foo_message = 3;
+ }
+}
+
+// Test messages for packed fields
+
+message TestPackedTypes {
+ repeated int32 packed_int32 = 90 [packed = true];
+ repeated int64 packed_int64 = 91 [packed = true];
+ repeated uint32 packed_uint32 = 92 [packed = true];
+ repeated uint64 packed_uint64 = 93 [packed = true];
+ repeated sint32 packed_sint32 = 94 [packed = true];
+ repeated sint64 packed_sint64 = 95 [packed = true];
+ repeated fixed32 packed_fixed32 = 96 [packed = true];
+ repeated fixed64 packed_fixed64 = 97 [packed = true];
+ repeated sfixed32 packed_sfixed32 = 98 [packed = true];
+ repeated sfixed64 packed_sfixed64 = 99 [packed = true];
+ repeated float packed_float = 100 [packed = true];
+ repeated double packed_double = 101 [packed = true];
+ repeated bool packed_bool = 102 [packed = true];
+ repeated ForeignEnum packed_enum = 103 [packed = true];
+}
+
+// A message with the same fields as TestPackedTypes, but without packing. Used
+// to test packed <-> unpacked wire compatibility.
+message TestUnpackedTypes {
+ repeated int32 unpacked_int32 = 90 [packed = false];
+ repeated int64 unpacked_int64 = 91 [packed = false];
+ repeated uint32 unpacked_uint32 = 92 [packed = false];
+ repeated uint64 unpacked_uint64 = 93 [packed = false];
+ repeated sint32 unpacked_sint32 = 94 [packed = false];
+ repeated sint64 unpacked_sint64 = 95 [packed = false];
+ repeated fixed32 unpacked_fixed32 = 96 [packed = false];
+ repeated fixed64 unpacked_fixed64 = 97 [packed = false];
+ repeated sfixed32 unpacked_sfixed32 = 98 [packed = false];
+ repeated sfixed64 unpacked_sfixed64 = 99 [packed = false];
+ repeated float unpacked_float = 100 [packed = false];
+ repeated double unpacked_double = 101 [packed = false];
+ repeated bool unpacked_bool = 102 [packed = false];
+ repeated ForeignEnum unpacked_enum = 103 [packed = false];
+}
+
+message TestRepeatedScalarDifferentTagSizes {
+ // Parsing repeated fixed size values used to fail. This message needs to be
+ // used in order to get a tag of the right size; all of the repeated fields
+ // in TestAllTypes didn't trigger the check.
+ repeated fixed32 repeated_fixed32 = 12;
+ // Check for a varint type, just for good measure.
+ repeated int32 repeated_int32 = 13;
+
+ // These have two-byte tags.
+ repeated fixed64 repeated_fixed64 = 2046;
+ repeated int64 repeated_int64 = 2047;
+
+ // Three byte tags.
+ repeated float repeated_float = 262142;
+ repeated uint64 repeated_uint64 = 262143;
+}
+
+message TestCommentInjectionMessage {
+ // */ <- This should not close the generated doc comment
+ string a = 1;
+}
+
+
+// Test that RPC services work.
+message FooRequest {}
+message FooResponse {}
+
+message FooClientMessage {}
+message FooServerMessage{}
+
+service TestService {
+ rpc Foo(FooRequest) returns (FooResponse);
+ rpc Bar(BarRequest) returns (BarResponse);
+}
+
+
+message BarRequest {}
+message BarResponse {}
+
diff --git a/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_well_known_types.proto b/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_well_known_types.proto
new file mode 100644
index 00000000..c9075244
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/protos/src/google/protobuf/unittest_well_known_types.proto
@@ -0,0 +1,114 @@
+syntax = "proto3";
+
+package protobuf_unittest;
+
+option csharp_namespace = "Google.Protobuf.TestProtos";
+option java_multiple_files = true;
+option java_package = "com.google.protobuf.test";
+
+import "google/protobuf/any.proto";
+import "google/protobuf/api.proto";
+import "google/protobuf/duration.proto";
+import "google/protobuf/empty.proto";
+import "google/protobuf/field_mask.proto";
+import "google/protobuf/source_context.proto";
+import "google/protobuf/struct.proto";
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/type.proto";
+import "google/protobuf/wrappers.proto";
+
+// Test that we can include all well-known types.
+// Each wrapper type is included separately, as languages
+// map handle different wrappers in different ways.
+message TestWellKnownTypes {
+ google.protobuf.Any any_field = 1;
+ google.protobuf.Api api_field = 2;
+ google.protobuf.Duration duration_field = 3;
+ google.protobuf.Empty empty_field = 4;
+ google.protobuf.FieldMask field_mask_field = 5;
+ google.protobuf.SourceContext source_context_field = 6;
+ google.protobuf.Struct struct_field = 7;
+ google.protobuf.Timestamp timestamp_field = 8;
+ google.protobuf.Type type_field = 9;
+ google.protobuf.DoubleValue double_field = 10;
+ google.protobuf.FloatValue float_field = 11;
+ google.protobuf.Int64Value int64_field = 12;
+ google.protobuf.UInt64Value uint64_field = 13;
+ google.protobuf.Int32Value int32_field = 14;
+ google.protobuf.UInt32Value uint32_field = 15;
+ google.protobuf.BoolValue bool_field = 16;
+ google.protobuf.StringValue string_field = 17;
+ google.protobuf.BytesValue bytes_field = 18;
+ // Part of struct, but useful to be able to test separately
+ google.protobuf.Value value_field = 19;
+}
+
+// A repeated field for each well-known type.
+message RepeatedWellKnownTypes {
+ repeated google.protobuf.Any any_field = 1;
+ repeated google.protobuf.Api api_field = 2;
+ repeated google.protobuf.Duration duration_field = 3;
+ repeated google.protobuf.Empty empty_field = 4;
+ repeated google.protobuf.FieldMask field_mask_field = 5;
+ repeated google.protobuf.SourceContext source_context_field = 6;
+ repeated google.protobuf.Struct struct_field = 7;
+ repeated google.protobuf.Timestamp timestamp_field = 8;
+ repeated google.protobuf.Type type_field = 9;
+ // These don't actually make a lot of sense, but they're not prohibited...
+ repeated google.protobuf.DoubleValue double_field = 10;
+ repeated google.protobuf.FloatValue float_field = 11;
+ repeated google.protobuf.Int64Value int64_field = 12;
+ repeated google.protobuf.UInt64Value uint64_field = 13;
+ repeated google.protobuf.Int32Value int32_field = 14;
+ repeated google.protobuf.UInt32Value uint32_field = 15;
+ repeated google.protobuf.BoolValue bool_field = 16;
+ repeated google.protobuf.StringValue string_field = 17;
+ repeated google.protobuf.BytesValue bytes_field = 18;
+}
+
+message OneofWellKnownTypes {
+ oneof oneof_field {
+ google.protobuf.Any any_field = 1;
+ google.protobuf.Api api_field = 2;
+ google.protobuf.Duration duration_field = 3;
+ google.protobuf.Empty empty_field = 4;
+ google.protobuf.FieldMask field_mask_field = 5;
+ google.protobuf.SourceContext source_context_field = 6;
+ google.protobuf.Struct struct_field = 7;
+ google.protobuf.Timestamp timestamp_field = 8;
+ google.protobuf.Type type_field = 9;
+ google.protobuf.DoubleValue double_field = 10;
+ google.protobuf.FloatValue float_field = 11;
+ google.protobuf.Int64Value int64_field = 12;
+ google.protobuf.UInt64Value uint64_field = 13;
+ google.protobuf.Int32Value int32_field = 14;
+ google.protobuf.UInt32Value uint32_field = 15;
+ google.protobuf.BoolValue bool_field = 16;
+ google.protobuf.StringValue string_field = 17;
+ google.protobuf.BytesValue bytes_field = 18;
+ }
+}
+
+// A map field for each well-known type. We only
+// need to worry about the value part of the map being the
+// well-known types, as messages can't be map keys.
+message MapWellKnownTypes {
+ map<int32,google.protobuf.Any> any_field = 1;
+ map<int32,google.protobuf.Api> api_field = 2;
+ map<int32,google.protobuf.Duration> duration_field = 3;
+ map<int32,google.protobuf.Empty> empty_field = 4;
+ map<int32,google.protobuf.FieldMask> field_mask_field = 5;
+ map<int32,google.protobuf.SourceContext> source_context_field = 6;
+ map<int32,google.protobuf.Struct> struct_field = 7;
+ map<int32,google.protobuf.Timestamp> timestamp_field = 8;
+ map<int32,google.protobuf.Type> type_field = 9;
+ map<int32,google.protobuf.DoubleValue> double_field = 10;
+ map<int32,google.protobuf.FloatValue> float_field = 11;
+ map<int32,google.protobuf.Int64Value> int64_field = 12;
+ map<int32,google.protobuf.UInt64Value> uint64_field = 13;
+ map<int32,google.protobuf.Int32Value> int32_field = 14;
+ map<int32,google.protobuf.UInt32Value> uint32_field = 15;
+ map<int32,google.protobuf.BoolValue> bool_field = 16;
+ map<int32,google.protobuf.StringValue> string_field = 17;
+ map<int32,google.protobuf.BytesValue> bytes_field = 18;
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/ByteStringTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/ByteStringTest.cs
new file mode 100644
index 00000000..685e130a
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/ByteStringTest.cs
@@ -0,0 +1,171 @@
+#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.Text;
+using NUnit.Framework;
+
+namespace Google.Protobuf
+{
+ public class ByteStringTest
+ {
+ [Test]
+ public void Equality()
+ {
+ ByteString b1 = ByteString.CopyFrom(1, 2, 3);
+ ByteString b2 = ByteString.CopyFrom(1, 2, 3);
+ ByteString b3 = ByteString.CopyFrom(1, 2, 4);
+ ByteString b4 = ByteString.CopyFrom(1, 2, 3, 4);
+ EqualityTester.AssertEquality(b1, b1);
+ EqualityTester.AssertEquality(b1, b2);
+ EqualityTester.AssertInequality(b1, b3);
+ EqualityTester.AssertInequality(b1, b4);
+ EqualityTester.AssertInequality(b1, null);
+#pragma warning disable 1718 // Deliberately calling ==(b1, b1) and !=(b1, b1)
+ Assert.IsTrue(b1 == b1);
+ Assert.IsTrue(b1 == b2);
+ Assert.IsFalse(b1 == b3);
+ Assert.IsFalse(b1 == b4);
+ Assert.IsFalse(b1 == null);
+ Assert.IsTrue((ByteString) null == null);
+ Assert.IsFalse(b1 != b1);
+ Assert.IsFalse(b1 != b2);
+#pragma warning disable 1718
+ Assert.IsTrue(b1 != b3);
+ Assert.IsTrue(b1 != b4);
+ Assert.IsTrue(b1 != null);
+ Assert.IsFalse((ByteString) null != null);
+ }
+
+ [Test]
+ public void EmptyByteStringHasZeroSize()
+ {
+ Assert.AreEqual(0, ByteString.Empty.Length);
+ }
+
+ [Test]
+ public void CopyFromStringWithExplicitEncoding()
+ {
+ ByteString bs = ByteString.CopyFrom("AB", Encoding.Unicode);
+ Assert.AreEqual(4, bs.Length);
+ Assert.AreEqual(65, bs[0]);
+ Assert.AreEqual(0, bs[1]);
+ Assert.AreEqual(66, bs[2]);
+ Assert.AreEqual(0, bs[3]);
+ }
+
+ [Test]
+ public void IsEmptyWhenEmpty()
+ {
+ Assert.IsTrue(ByteString.CopyFromUtf8("").IsEmpty);
+ }
+
+ [Test]
+ public void IsEmptyWhenNotEmpty()
+ {
+ Assert.IsFalse(ByteString.CopyFromUtf8("X").IsEmpty);
+ }
+
+ [Test]
+ public void CopyFromByteArrayCopiesContents()
+ {
+ byte[] data = new byte[1];
+ data[0] = 10;
+ ByteString bs = ByteString.CopyFrom(data);
+ Assert.AreEqual(10, bs[0]);
+ data[0] = 5;
+ Assert.AreEqual(10, bs[0]);
+ }
+
+ [Test]
+ public void ToByteArrayCopiesContents()
+ {
+ ByteString bs = ByteString.CopyFromUtf8("Hello");
+ byte[] data = bs.ToByteArray();
+ Assert.AreEqual((byte)'H', data[0]);
+ Assert.AreEqual((byte)'H', bs[0]);
+ data[0] = 0;
+ Assert.AreEqual(0, data[0]);
+ Assert.AreEqual((byte)'H', bs[0]);
+ }
+
+ [Test]
+ public void CopyFromUtf8UsesUtf8()
+ {
+ ByteString bs = ByteString.CopyFromUtf8("\u20ac");
+ Assert.AreEqual(3, bs.Length);
+ Assert.AreEqual(0xe2, bs[0]);
+ Assert.AreEqual(0x82, bs[1]);
+ Assert.AreEqual(0xac, bs[2]);
+ }
+
+ [Test]
+ public void CopyFromPortion()
+ {
+ byte[] data = new byte[] {0, 1, 2, 3, 4, 5, 6};
+ ByteString bs = ByteString.CopyFrom(data, 2, 3);
+ Assert.AreEqual(3, bs.Length);
+ Assert.AreEqual(2, bs[0]);
+ Assert.AreEqual(3, bs[1]);
+ }
+
+ [Test]
+ public void ToStringUtf8()
+ {
+ ByteString bs = ByteString.CopyFromUtf8("\u20ac");
+ Assert.AreEqual("\u20ac", bs.ToStringUtf8());
+ }
+
+ [Test]
+ public void ToStringWithExplicitEncoding()
+ {
+ ByteString bs = ByteString.CopyFrom("\u20ac", Encoding.Unicode);
+ Assert.AreEqual("\u20ac", bs.ToString(Encoding.Unicode));
+ }
+
+ [Test]
+ public void FromBase64_WithText()
+ {
+ byte[] data = new byte[] {0, 1, 2, 3, 4, 5, 6};
+ string base64 = Convert.ToBase64String(data);
+ ByteString bs = ByteString.FromBase64(base64);
+ Assert.AreEqual(data, bs.ToByteArray());
+ }
+
+ [Test]
+ public void FromBase64_Empty()
+ {
+ // Optimization which also fixes issue 61.
+ Assert.AreSame(ByteString.Empty, ByteString.FromBase64(""));
+ }
+ }
+} \ No newline at end of file
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/CodedInputStreamExtensions.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/CodedInputStreamExtensions.cs
new file mode 100644
index 00000000..23af2887
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/CodedInputStreamExtensions.cs
@@ -0,0 +1,53 @@
+#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;
+
+namespace Google.Protobuf
+{
+ internal static class CodedInputStreamExtensions
+ {
+ public static void AssertNextTag(this CodedInputStream input, uint expectedTag)
+ {
+ uint tag = input.ReadTag();
+ Assert.AreEqual(expectedTag, tag);
+ }
+
+ public static T ReadMessage<T>(this CodedInputStream stream, MessageParser<T> parser)
+ where T : IMessage<T>
+ {
+ var message = parser.CreateTemplate();
+ stream.ReadMessage(message);
+ return message;
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/CodedInputStreamTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/CodedInputStreamTest.cs
new file mode 100644
index 00000000..c0a9ffd1
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/CodedInputStreamTest.cs
@@ -0,0 +1,598 @@
+#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.IO;
+using Google.Protobuf.TestProtos;
+using NUnit.Framework;
+
+namespace Google.Protobuf
+{
+ public class CodedInputStreamTest
+ {
+ /// <summary>
+ /// Helper to construct a byte array from a bunch of bytes. The inputs are
+ /// actually ints so that I can use hex notation and not get stupid errors
+ /// about precision.
+ /// </summary>
+ private static byte[] Bytes(params int[] bytesAsInts)
+ {
+ byte[] bytes = new byte[bytesAsInts.Length];
+ for (int i = 0; i < bytesAsInts.Length; i++)
+ {
+ bytes[i] = (byte) bytesAsInts[i];
+ }
+ return bytes;
+ }
+
+ /// <summary>
+ /// Parses the given bytes using ReadRawVarint32() and ReadRawVarint64()
+ /// </summary>
+ private static void AssertReadVarint(byte[] data, ulong value)
+ {
+ CodedInputStream input = new CodedInputStream(data);
+ Assert.AreEqual((uint) value, input.ReadRawVarint32());
+
+ input = new CodedInputStream(data);
+ Assert.AreEqual(value, input.ReadRawVarint64());
+ Assert.IsTrue(input.IsAtEnd);
+
+ // Try different block sizes.
+ for (int bufferSize = 1; bufferSize <= 16; bufferSize *= 2)
+ {
+ input = new CodedInputStream(new SmallBlockInputStream(data, bufferSize));
+ Assert.AreEqual((uint) value, input.ReadRawVarint32());
+
+ input = new CodedInputStream(new SmallBlockInputStream(data, bufferSize));
+ Assert.AreEqual(value, input.ReadRawVarint64());
+ Assert.IsTrue(input.IsAtEnd);
+ }
+
+ // Try reading directly from a MemoryStream. We want to verify that it
+ // doesn't read past the end of the input, so write an extra byte - this
+ // lets us test the position at the end.
+ MemoryStream memoryStream = new MemoryStream();
+ memoryStream.Write(data, 0, data.Length);
+ memoryStream.WriteByte(0);
+ memoryStream.Position = 0;
+ Assert.AreEqual((uint) value, CodedInputStream.ReadRawVarint32(memoryStream));
+ Assert.AreEqual(data.Length, memoryStream.Position);
+ }
+
+ /// <summary>
+ /// Parses the given bytes using ReadRawVarint32() and ReadRawVarint64() and
+ /// expects them to fail with an InvalidProtocolBufferException whose
+ /// description matches the given one.
+ /// </summary>
+ private static void AssertReadVarintFailure(InvalidProtocolBufferException expected, byte[] data)
+ {
+ CodedInputStream input = new CodedInputStream(data);
+ var exception = Assert.Throws<InvalidProtocolBufferException>(() => input.ReadRawVarint32());
+ Assert.AreEqual(expected.Message, exception.Message);
+
+ input = new CodedInputStream(data);
+ exception = Assert.Throws<InvalidProtocolBufferException>(() => input.ReadRawVarint64());
+ Assert.AreEqual(expected.Message, exception.Message);
+
+ // Make sure we get the same error when reading directly from a Stream.
+ exception = Assert.Throws<InvalidProtocolBufferException>(() => CodedInputStream.ReadRawVarint32(new MemoryStream(data)));
+ Assert.AreEqual(expected.Message, exception.Message);
+ }
+
+ [Test]
+ public void ReadVarint()
+ {
+ AssertReadVarint(Bytes(0x00), 0);
+ AssertReadVarint(Bytes(0x01), 1);
+ AssertReadVarint(Bytes(0x7f), 127);
+ // 14882
+ AssertReadVarint(Bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7));
+ // 2961488830
+ AssertReadVarint(Bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b),
+ (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) |
+ (0x0bL << 28));
+
+ // 64-bit
+ // 7256456126
+ AssertReadVarint(Bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b),
+ (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) |
+ (0x1bL << 28));
+ // 41256202580718336
+ AssertReadVarint(Bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49),
+ (0x00 << 0) | (0x66 << 7) | (0x6b << 14) | (0x1c << 21) |
+ (0x43L << 28) | (0x49L << 35) | (0x24L << 42) | (0x49L << 49));
+ // 11964378330978735131
+ AssertReadVarint(Bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01),
+ (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) |
+ (0x3bUL << 28) | (0x56UL << 35) | (0x00UL << 42) |
+ (0x05UL << 49) | (0x26UL << 56) | (0x01UL << 63));
+
+ // Failures
+ AssertReadVarintFailure(
+ InvalidProtocolBufferException.MalformedVarint(),
+ Bytes(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x00));
+ AssertReadVarintFailure(
+ InvalidProtocolBufferException.TruncatedMessage(),
+ Bytes(0x80));
+ }
+
+ /// <summary>
+ /// Parses the given bytes using ReadRawLittleEndian32() and checks
+ /// that the result matches the given value.
+ /// </summary>
+ private static void AssertReadLittleEndian32(byte[] data, uint value)
+ {
+ CodedInputStream input = new CodedInputStream(data);
+ Assert.AreEqual(value, input.ReadRawLittleEndian32());
+ Assert.IsTrue(input.IsAtEnd);
+
+ // Try different block sizes.
+ for (int blockSize = 1; blockSize <= 16; blockSize *= 2)
+ {
+ input = new CodedInputStream(
+ new SmallBlockInputStream(data, blockSize));
+ Assert.AreEqual(value, input.ReadRawLittleEndian32());
+ Assert.IsTrue(input.IsAtEnd);
+ }
+ }
+
+ /// <summary>
+ /// Parses the given bytes using ReadRawLittleEndian64() and checks
+ /// that the result matches the given value.
+ /// </summary>
+ private static void AssertReadLittleEndian64(byte[] data, ulong value)
+ {
+ CodedInputStream input = new CodedInputStream(data);
+ Assert.AreEqual(value, input.ReadRawLittleEndian64());
+ Assert.IsTrue(input.IsAtEnd);
+
+ // Try different block sizes.
+ for (int blockSize = 1; blockSize <= 16; blockSize *= 2)
+ {
+ input = new CodedInputStream(
+ new SmallBlockInputStream(data, blockSize));
+ Assert.AreEqual(value, input.ReadRawLittleEndian64());
+ Assert.IsTrue(input.IsAtEnd);
+ }
+ }
+
+ [Test]
+ public void ReadLittleEndian()
+ {
+ AssertReadLittleEndian32(Bytes(0x78, 0x56, 0x34, 0x12), 0x12345678);
+ AssertReadLittleEndian32(Bytes(0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef0);
+
+ AssertReadLittleEndian64(Bytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12),
+ 0x123456789abcdef0L);
+ AssertReadLittleEndian64(
+ Bytes(0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef012345678UL);
+ }
+
+ [Test]
+ public void DecodeZigZag32()
+ {
+ Assert.AreEqual(0, CodedInputStream.DecodeZigZag32(0));
+ Assert.AreEqual(-1, CodedInputStream.DecodeZigZag32(1));
+ Assert.AreEqual(1, CodedInputStream.DecodeZigZag32(2));
+ Assert.AreEqual(-2, CodedInputStream.DecodeZigZag32(3));
+ Assert.AreEqual(0x3FFFFFFF, CodedInputStream.DecodeZigZag32(0x7FFFFFFE));
+ Assert.AreEqual(unchecked((int) 0xC0000000), CodedInputStream.DecodeZigZag32(0x7FFFFFFF));
+ Assert.AreEqual(0x7FFFFFFF, CodedInputStream.DecodeZigZag32(0xFFFFFFFE));
+ Assert.AreEqual(unchecked((int) 0x80000000), CodedInputStream.DecodeZigZag32(0xFFFFFFFF));
+ }
+
+ [Test]
+ public void DecodeZigZag64()
+ {
+ Assert.AreEqual(0, CodedInputStream.DecodeZigZag64(0));
+ Assert.AreEqual(-1, CodedInputStream.DecodeZigZag64(1));
+ Assert.AreEqual(1, CodedInputStream.DecodeZigZag64(2));
+ Assert.AreEqual(-2, CodedInputStream.DecodeZigZag64(3));
+ Assert.AreEqual(0x000000003FFFFFFFL, CodedInputStream.DecodeZigZag64(0x000000007FFFFFFEL));
+ Assert.AreEqual(unchecked((long) 0xFFFFFFFFC0000000L), CodedInputStream.DecodeZigZag64(0x000000007FFFFFFFL));
+ Assert.AreEqual(0x000000007FFFFFFFL, CodedInputStream.DecodeZigZag64(0x00000000FFFFFFFEL));
+ Assert.AreEqual(unchecked((long) 0xFFFFFFFF80000000L), CodedInputStream.DecodeZigZag64(0x00000000FFFFFFFFL));
+ Assert.AreEqual(0x7FFFFFFFFFFFFFFFL, CodedInputStream.DecodeZigZag64(0xFFFFFFFFFFFFFFFEL));
+ Assert.AreEqual(unchecked((long) 0x8000000000000000L), CodedInputStream.DecodeZigZag64(0xFFFFFFFFFFFFFFFFL));
+ }
+
+ [Test]
+ public void ReadWholeMessage_VaryingBlockSizes()
+ {
+ TestAllTypes message = SampleMessages.CreateFullTestAllTypes();
+
+ byte[] rawBytes = message.ToByteArray();
+ Assert.AreEqual(rawBytes.Length, message.CalculateSize());
+ TestAllTypes message2 = TestAllTypes.Parser.ParseFrom(rawBytes);
+ Assert.AreEqual(message, message2);
+
+ // Try different block sizes.
+ for (int blockSize = 1; blockSize < 256; blockSize *= 2)
+ {
+ message2 = TestAllTypes.Parser.ParseFrom(new SmallBlockInputStream(rawBytes, blockSize));
+ Assert.AreEqual(message, message2);
+ }
+ }
+
+ [Test]
+ public void ReadHugeBlob()
+ {
+ // Allocate and initialize a 1MB blob.
+ byte[] blob = new byte[1 << 20];
+ for (int i = 0; i < blob.Length; i++)
+ {
+ blob[i] = (byte) i;
+ }
+
+ // Make a message containing it.
+ var message = new TestAllTypes { SingleBytes = ByteString.CopyFrom(blob) };
+
+ // Serialize and parse it. Make sure to parse from an InputStream, not
+ // directly from a ByteString, so that CodedInputStream uses buffered
+ // reading.
+ TestAllTypes message2 = TestAllTypes.Parser.ParseFrom(message.ToByteString());
+
+ Assert.AreEqual(message, message2);
+ }
+
+ [Test]
+ public void ReadMaliciouslyLargeBlob()
+ {
+ MemoryStream ms = new MemoryStream();
+ CodedOutputStream output = new CodedOutputStream(ms);
+
+ uint tag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
+ output.WriteRawVarint32(tag);
+ output.WriteRawVarint32(0x7FFFFFFF);
+ output.WriteRawBytes(new byte[32]); // Pad with a few random bytes.
+ output.Flush();
+ ms.Position = 0;
+
+ CodedInputStream input = new CodedInputStream(ms);
+ Assert.AreEqual(tag, input.ReadTag());
+
+ Assert.Throws<InvalidProtocolBufferException>(() => input.ReadBytes());
+ }
+
+ internal static TestRecursiveMessage MakeRecursiveMessage(int depth)
+ {
+ if (depth == 0)
+ {
+ return new TestRecursiveMessage { I = 5 };
+ }
+ else
+ {
+ return new TestRecursiveMessage { A = MakeRecursiveMessage(depth - 1) };
+ }
+ }
+
+ internal static void AssertMessageDepth(TestRecursiveMessage message, int depth)
+ {
+ if (depth == 0)
+ {
+ Assert.IsNull(message.A);
+ Assert.AreEqual(5, message.I);
+ }
+ else
+ {
+ Assert.IsNotNull(message.A);
+ AssertMessageDepth(message.A, depth - 1);
+ }
+ }
+
+ [Test]
+ public void MaliciousRecursion()
+ {
+ ByteString data64 = MakeRecursiveMessage(64).ToByteString();
+ ByteString data65 = MakeRecursiveMessage(65).ToByteString();
+
+ AssertMessageDepth(TestRecursiveMessage.Parser.ParseFrom(data64), 64);
+
+ Assert.Throws<InvalidProtocolBufferException>(() => TestRecursiveMessage.Parser.ParseFrom(data65));
+
+ CodedInputStream input = CodedInputStream.CreateWithLimits(new MemoryStream(data64.ToByteArray()), 1000000, 63);
+ Assert.Throws<InvalidProtocolBufferException>(() => TestRecursiveMessage.Parser.ParseFrom(input));
+ }
+
+ [Test]
+ public void SizeLimit()
+ {
+ // Have to use a Stream rather than ByteString.CreateCodedInput as SizeLimit doesn't
+ // apply to the latter case.
+ MemoryStream ms = new MemoryStream(SampleMessages.CreateFullTestAllTypes().ToByteArray());
+ CodedInputStream input = CodedInputStream.CreateWithLimits(ms, 16, 100);
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseFrom(input));
+ }
+
+ /// <summary>
+ /// Tests that if we read an string that contains invalid UTF-8, no exception
+ /// is thrown. Instead, the invalid bytes are replaced with the Unicode
+ /// "replacement character" U+FFFD.
+ /// </summary>
+ [Test]
+ public void ReadInvalidUtf8()
+ {
+ MemoryStream ms = new MemoryStream();
+ CodedOutputStream output = new CodedOutputStream(ms);
+
+ uint tag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
+ output.WriteRawVarint32(tag);
+ output.WriteRawVarint32(1);
+ output.WriteRawBytes(new byte[] {0x80});
+ output.Flush();
+ ms.Position = 0;
+
+ CodedInputStream input = new CodedInputStream(ms);
+
+ Assert.AreEqual(tag, input.ReadTag());
+ string text = input.ReadString();
+ Assert.AreEqual('\ufffd', text[0]);
+ }
+
+ /// <summary>
+ /// A stream which limits the number of bytes it reads at a time.
+ /// We use this to make sure that CodedInputStream doesn't screw up when
+ /// reading in small blocks.
+ /// </summary>
+ private sealed class SmallBlockInputStream : MemoryStream
+ {
+ private readonly int blockSize;
+
+ public SmallBlockInputStream(byte[] data, int blockSize)
+ : base(data)
+ {
+ this.blockSize = blockSize;
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return base.Read(buffer, offset, Math.Min(count, blockSize));
+ }
+ }
+
+ [Test]
+ public void TestNegativeEnum()
+ {
+ byte[] bytes = { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 };
+ CodedInputStream input = new CodedInputStream(bytes);
+ Assert.AreEqual((int)SampleEnum.NegativeValue, input.ReadEnum());
+ Assert.IsTrue(input.IsAtEnd);
+ }
+
+ //Issue 71: CodedInputStream.ReadBytes go to slow path unnecessarily
+ [Test]
+ public void TestSlowPathAvoidance()
+ {
+ using (var ms = new MemoryStream())
+ {
+ CodedOutputStream output = new CodedOutputStream(ms);
+ output.WriteTag(1, WireFormat.WireType.LengthDelimited);
+ output.WriteBytes(ByteString.CopyFrom(new byte[100]));
+ output.WriteTag(2, WireFormat.WireType.LengthDelimited);
+ output.WriteBytes(ByteString.CopyFrom(new byte[100]));
+ output.Flush();
+
+ ms.Position = 0;
+ CodedInputStream input = new CodedInputStream(ms, new byte[ms.Length / 2], 0, 0);
+
+ uint tag = input.ReadTag();
+ Assert.AreEqual(1, WireFormat.GetTagFieldNumber(tag));
+ Assert.AreEqual(100, input.ReadBytes().Length);
+
+ tag = input.ReadTag();
+ Assert.AreEqual(2, WireFormat.GetTagFieldNumber(tag));
+ Assert.AreEqual(100, input.ReadBytes().Length);
+ }
+ }
+
+ [Test]
+ public void Tag0Throws()
+ {
+ var input = new CodedInputStream(new byte[] { 0 });
+ Assert.Throws<InvalidProtocolBufferException>(() => input.ReadTag());
+ }
+
+ [Test]
+ public void SkipGroup()
+ {
+ // Create an output stream with a group in:
+ // Field 1: string "field 1"
+ // Field 2: group containing:
+ // Field 1: fixed int32 value 100
+ // Field 2: string "ignore me"
+ // Field 3: nested group containing
+ // Field 1: fixed int64 value 1000
+ // Field 3: string "field 3"
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ output.WriteTag(1, WireFormat.WireType.LengthDelimited);
+ output.WriteString("field 1");
+
+ // The outer group...
+ output.WriteTag(2, WireFormat.WireType.StartGroup);
+ output.WriteTag(1, WireFormat.WireType.Fixed32);
+ output.WriteFixed32(100);
+ output.WriteTag(2, WireFormat.WireType.LengthDelimited);
+ output.WriteString("ignore me");
+ // The nested group...
+ output.WriteTag(3, WireFormat.WireType.StartGroup);
+ output.WriteTag(1, WireFormat.WireType.Fixed64);
+ output.WriteFixed64(1000);
+ // Note: Not sure the field number is relevant for end group...
+ output.WriteTag(3, WireFormat.WireType.EndGroup);
+
+ // End the outer group
+ output.WriteTag(2, WireFormat.WireType.EndGroup);
+
+ output.WriteTag(3, WireFormat.WireType.LengthDelimited);
+ output.WriteString("field 3");
+ output.Flush();
+ stream.Position = 0;
+
+ // Now act like a generated client
+ var input = new CodedInputStream(stream);
+ Assert.AreEqual(WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited), input.ReadTag());
+ Assert.AreEqual("field 1", input.ReadString());
+ Assert.AreEqual(WireFormat.MakeTag(2, WireFormat.WireType.StartGroup), input.ReadTag());
+ input.SkipLastField(); // Should consume the whole group, including the nested one.
+ Assert.AreEqual(WireFormat.MakeTag(3, WireFormat.WireType.LengthDelimited), input.ReadTag());
+ Assert.AreEqual("field 3", input.ReadString());
+ }
+
+ [Test]
+ public void SkipGroup_WrongEndGroupTag()
+ {
+ // Create an output stream with:
+ // Field 1: string "field 1"
+ // Start group 2
+ // Field 3: fixed int32
+ // End group 4 (should give an error)
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ output.WriteTag(1, WireFormat.WireType.LengthDelimited);
+ output.WriteString("field 1");
+
+ // The outer group...
+ output.WriteTag(2, WireFormat.WireType.StartGroup);
+ output.WriteTag(3, WireFormat.WireType.Fixed32);
+ output.WriteFixed32(100);
+ output.WriteTag(4, WireFormat.WireType.EndGroup);
+ output.Flush();
+ stream.Position = 0;
+
+ // Now act like a generated client
+ var input = new CodedInputStream(stream);
+ Assert.AreEqual(WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited), input.ReadTag());
+ Assert.AreEqual("field 1", input.ReadString());
+ Assert.AreEqual(WireFormat.MakeTag(2, WireFormat.WireType.StartGroup), input.ReadTag());
+ Assert.Throws<InvalidProtocolBufferException>(input.SkipLastField);
+ }
+
+ [Test]
+ public void RogueEndGroupTag()
+ {
+ // If we have an end-group tag without a leading start-group tag, generated
+ // code will just call SkipLastField... so that should fail.
+
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ output.WriteTag(1, WireFormat.WireType.EndGroup);
+ output.Flush();
+ stream.Position = 0;
+
+ var input = new CodedInputStream(stream);
+ Assert.AreEqual(WireFormat.MakeTag(1, WireFormat.WireType.EndGroup), input.ReadTag());
+ Assert.Throws<InvalidProtocolBufferException>(input.SkipLastField);
+ }
+
+ [Test]
+ public void EndOfStreamReachedWhileSkippingGroup()
+ {
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ output.WriteTag(1, WireFormat.WireType.StartGroup);
+ output.WriteTag(2, WireFormat.WireType.StartGroup);
+ output.WriteTag(2, WireFormat.WireType.EndGroup);
+
+ output.Flush();
+ stream.Position = 0;
+
+ // Now act like a generated client
+ var input = new CodedInputStream(stream);
+ input.ReadTag();
+ Assert.Throws<InvalidProtocolBufferException>(input.SkipLastField);
+ }
+
+ [Test]
+ public void RecursionLimitAppliedWhileSkippingGroup()
+ {
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ for (int i = 0; i < CodedInputStream.DefaultRecursionLimit + 1; i++)
+ {
+ output.WriteTag(1, WireFormat.WireType.StartGroup);
+ }
+ for (int i = 0; i < CodedInputStream.DefaultRecursionLimit + 1; i++)
+ {
+ output.WriteTag(1, WireFormat.WireType.EndGroup);
+ }
+ output.Flush();
+ stream.Position = 0;
+
+ // Now act like a generated client
+ var input = new CodedInputStream(stream);
+ Assert.AreEqual(WireFormat.MakeTag(1, WireFormat.WireType.StartGroup), input.ReadTag());
+ Assert.Throws<InvalidProtocolBufferException>(input.SkipLastField);
+ }
+
+ [Test]
+ public void Construction_Invalid()
+ {
+ Assert.Throws<ArgumentNullException>(() => new CodedInputStream((byte[]) null));
+ Assert.Throws<ArgumentNullException>(() => new CodedInputStream(null, 0, 0));
+ Assert.Throws<ArgumentNullException>(() => new CodedInputStream((Stream) null));
+ Assert.Throws<ArgumentOutOfRangeException>(() => new CodedInputStream(new byte[10], 100, 0));
+ Assert.Throws<ArgumentOutOfRangeException>(() => new CodedInputStream(new byte[10], 5, 10));
+ }
+
+ [Test]
+ public void CreateWithLimits_InvalidLimits()
+ {
+ var stream = new MemoryStream();
+ Assert.Throws<ArgumentOutOfRangeException>(() => CodedInputStream.CreateWithLimits(stream, 0, 1));
+ Assert.Throws<ArgumentOutOfRangeException>(() => CodedInputStream.CreateWithLimits(stream, 1, 0));
+ }
+
+ [Test]
+ public void Dispose_DisposesUnderlyingStream()
+ {
+ var memoryStream = new MemoryStream();
+ Assert.IsTrue(memoryStream.CanRead);
+ using (var cis = new CodedInputStream(memoryStream))
+ {
+ }
+ Assert.IsFalse(memoryStream.CanRead); // Disposed
+ }
+
+ [Test]
+ public void Dispose_WithLeaveOpen()
+ {
+ var memoryStream = new MemoryStream();
+ Assert.IsTrue(memoryStream.CanRead);
+ using (var cis = new CodedInputStream(memoryStream, true))
+ {
+ }
+ Assert.IsTrue(memoryStream.CanRead); // We left the stream open
+ }
+ }
+} \ No newline at end of file
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/CodedOutputStreamTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/CodedOutputStreamTest.cs
new file mode 100644
index 00000000..48bf6d60
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/CodedOutputStreamTest.cs
@@ -0,0 +1,419 @@
+#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.IO;
+using Google.Protobuf.TestProtos;
+using NUnit.Framework;
+
+namespace Google.Protobuf
+{
+ public class CodedOutputStreamTest
+ {
+ /// <summary>
+ /// Writes the given value using WriteRawVarint32() and WriteRawVarint64() and
+ /// checks that the result matches the given bytes
+ /// </summary>
+ private static void AssertWriteVarint(byte[] data, ulong value)
+ {
+ // Only do 32-bit write if the value fits in 32 bits.
+ if ((value >> 32) == 0)
+ {
+ MemoryStream rawOutput = new MemoryStream();
+ CodedOutputStream output = new CodedOutputStream(rawOutput);
+ output.WriteRawVarint32((uint) value);
+ output.Flush();
+ Assert.AreEqual(data, rawOutput.ToArray());
+ // Also try computing size.
+ Assert.AreEqual(data.Length, CodedOutputStream.ComputeRawVarint32Size((uint) value));
+ }
+
+ {
+ MemoryStream rawOutput = new MemoryStream();
+ CodedOutputStream output = new CodedOutputStream(rawOutput);
+ output.WriteRawVarint64(value);
+ output.Flush();
+ Assert.AreEqual(data, rawOutput.ToArray());
+
+ // Also try computing size.
+ Assert.AreEqual(data.Length, CodedOutputStream.ComputeRawVarint64Size(value));
+ }
+
+ // Try different buffer sizes.
+ for (int bufferSize = 1; bufferSize <= 16; bufferSize *= 2)
+ {
+ // Only do 32-bit write if the value fits in 32 bits.
+ if ((value >> 32) == 0)
+ {
+ MemoryStream rawOutput = new MemoryStream();
+ CodedOutputStream output =
+ new CodedOutputStream(rawOutput, bufferSize);
+ output.WriteRawVarint32((uint) value);
+ output.Flush();
+ Assert.AreEqual(data, rawOutput.ToArray());
+ }
+
+ {
+ MemoryStream rawOutput = new MemoryStream();
+ CodedOutputStream output = new CodedOutputStream(rawOutput, bufferSize);
+ output.WriteRawVarint64(value);
+ output.Flush();
+ Assert.AreEqual(data, rawOutput.ToArray());
+ }
+ }
+ }
+
+ /// <summary>
+ /// Tests WriteRawVarint32() and WriteRawVarint64()
+ /// </summary>
+ [Test]
+ public void WriteVarint()
+ {
+ AssertWriteVarint(new byte[] {0x00}, 0);
+ AssertWriteVarint(new byte[] {0x01}, 1);
+ AssertWriteVarint(new byte[] {0x7f}, 127);
+ // 14882
+ AssertWriteVarint(new byte[] {0xa2, 0x74}, (0x22 << 0) | (0x74 << 7));
+ // 2961488830
+ AssertWriteVarint(new byte[] {0xbe, 0xf7, 0x92, 0x84, 0x0b},
+ (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) |
+ (0x0bL << 28));
+
+ // 64-bit
+ // 7256456126
+ AssertWriteVarint(new byte[] {0xbe, 0xf7, 0x92, 0x84, 0x1b},
+ (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) |
+ (0x1bL << 28));
+ // 41256202580718336
+ AssertWriteVarint(
+ new byte[] {0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49},
+ (0x00 << 0) | (0x66 << 7) | (0x6b << 14) | (0x1c << 21) |
+ (0x43UL << 28) | (0x49L << 35) | (0x24UL << 42) | (0x49UL << 49));
+ // 11964378330978735131
+ AssertWriteVarint(
+ new byte[] {0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01},
+ unchecked((ulong)
+ ((0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) |
+ (0x3bL << 28) | (0x56L << 35) | (0x00L << 42) |
+ (0x05L << 49) | (0x26L << 56) | (0x01L << 63))));
+ }
+
+ /// <summary>
+ /// Parses the given bytes using WriteRawLittleEndian32() and checks
+ /// that the result matches the given value.
+ /// </summary>
+ private static void AssertWriteLittleEndian32(byte[] data, uint value)
+ {
+ MemoryStream rawOutput = new MemoryStream();
+ CodedOutputStream output = new CodedOutputStream(rawOutput);
+ output.WriteRawLittleEndian32(value);
+ output.Flush();
+ Assert.AreEqual(data, rawOutput.ToArray());
+
+ // Try different buffer sizes.
+ for (int bufferSize = 1; bufferSize <= 16; bufferSize *= 2)
+ {
+ rawOutput = new MemoryStream();
+ output = new CodedOutputStream(rawOutput, bufferSize);
+ output.WriteRawLittleEndian32(value);
+ output.Flush();
+ Assert.AreEqual(data, rawOutput.ToArray());
+ }
+ }
+
+ /// <summary>
+ /// Parses the given bytes using WriteRawLittleEndian64() and checks
+ /// that the result matches the given value.
+ /// </summary>
+ private static void AssertWriteLittleEndian64(byte[] data, ulong value)
+ {
+ MemoryStream rawOutput = new MemoryStream();
+ CodedOutputStream output = new CodedOutputStream(rawOutput);
+ output.WriteRawLittleEndian64(value);
+ output.Flush();
+ Assert.AreEqual(data, rawOutput.ToArray());
+
+ // Try different block sizes.
+ for (int blockSize = 1; blockSize <= 16; blockSize *= 2)
+ {
+ rawOutput = new MemoryStream();
+ output = new CodedOutputStream(rawOutput, blockSize);
+ output.WriteRawLittleEndian64(value);
+ output.Flush();
+ Assert.AreEqual(data, rawOutput.ToArray());
+ }
+ }
+
+ /// <summary>
+ /// Tests writeRawLittleEndian32() and writeRawLittleEndian64().
+ /// </summary>
+ [Test]
+ public void WriteLittleEndian()
+ {
+ AssertWriteLittleEndian32(new byte[] {0x78, 0x56, 0x34, 0x12}, 0x12345678);
+ AssertWriteLittleEndian32(new byte[] {0xf0, 0xde, 0xbc, 0x9a}, 0x9abcdef0);
+
+ AssertWriteLittleEndian64(
+ new byte[] {0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12},
+ 0x123456789abcdef0L);
+ AssertWriteLittleEndian64(
+ new byte[] {0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a},
+ 0x9abcdef012345678UL);
+ }
+
+ [Test]
+ public void WriteWholeMessage_VaryingBlockSizes()
+ {
+ TestAllTypes message = SampleMessages.CreateFullTestAllTypes();
+
+ byte[] rawBytes = message.ToByteArray();
+
+ // Try different block sizes.
+ for (int blockSize = 1; blockSize < 256; blockSize *= 2)
+ {
+ MemoryStream rawOutput = new MemoryStream();
+ CodedOutputStream output = new CodedOutputStream(rawOutput, blockSize);
+ message.WriteTo(output);
+ output.Flush();
+ Assert.AreEqual(rawBytes, rawOutput.ToArray());
+ }
+ }
+
+ [Test]
+ public void EncodeZigZag32()
+ {
+ Assert.AreEqual(0u, CodedOutputStream.EncodeZigZag32(0));
+ Assert.AreEqual(1u, CodedOutputStream.EncodeZigZag32(-1));
+ Assert.AreEqual(2u, CodedOutputStream.EncodeZigZag32(1));
+ Assert.AreEqual(3u, CodedOutputStream.EncodeZigZag32(-2));
+ Assert.AreEqual(0x7FFFFFFEu, CodedOutputStream.EncodeZigZag32(0x3FFFFFFF));
+ Assert.AreEqual(0x7FFFFFFFu, CodedOutputStream.EncodeZigZag32(unchecked((int) 0xC0000000)));
+ Assert.AreEqual(0xFFFFFFFEu, CodedOutputStream.EncodeZigZag32(0x7FFFFFFF));
+ Assert.AreEqual(0xFFFFFFFFu, CodedOutputStream.EncodeZigZag32(unchecked((int) 0x80000000)));
+ }
+
+ [Test]
+ public void EncodeZigZag64()
+ {
+ Assert.AreEqual(0u, CodedOutputStream.EncodeZigZag64(0));
+ Assert.AreEqual(1u, CodedOutputStream.EncodeZigZag64(-1));
+ Assert.AreEqual(2u, CodedOutputStream.EncodeZigZag64(1));
+ Assert.AreEqual(3u, CodedOutputStream.EncodeZigZag64(-2));
+ Assert.AreEqual(0x000000007FFFFFFEuL,
+ CodedOutputStream.EncodeZigZag64(unchecked((long) 0x000000003FFFFFFFUL)));
+ Assert.AreEqual(0x000000007FFFFFFFuL,
+ CodedOutputStream.EncodeZigZag64(unchecked((long) 0xFFFFFFFFC0000000UL)));
+ Assert.AreEqual(0x00000000FFFFFFFEuL,
+ CodedOutputStream.EncodeZigZag64(unchecked((long) 0x000000007FFFFFFFUL)));
+ Assert.AreEqual(0x00000000FFFFFFFFuL,
+ CodedOutputStream.EncodeZigZag64(unchecked((long) 0xFFFFFFFF80000000UL)));
+ Assert.AreEqual(0xFFFFFFFFFFFFFFFEL,
+ CodedOutputStream.EncodeZigZag64(unchecked((long) 0x7FFFFFFFFFFFFFFFUL)));
+ Assert.AreEqual(0xFFFFFFFFFFFFFFFFL,
+ CodedOutputStream.EncodeZigZag64(unchecked((long) 0x8000000000000000UL)));
+ }
+
+ [Test]
+ public void RoundTripZigZag32()
+ {
+ // Some easier-to-verify round-trip tests. The inputs (other than 0, 1, -1)
+ // were chosen semi-randomly via keyboard bashing.
+ Assert.AreEqual(0, CodedInputStream.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(0)));
+ Assert.AreEqual(1, CodedInputStream.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(1)));
+ Assert.AreEqual(-1, CodedInputStream.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(-1)));
+ Assert.AreEqual(14927, CodedInputStream.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(14927)));
+ Assert.AreEqual(-3612, CodedInputStream.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(-3612)));
+ }
+
+ [Test]
+ public void RoundTripZigZag64()
+ {
+ Assert.AreEqual(0, CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(0)));
+ Assert.AreEqual(1, CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(1)));
+ Assert.AreEqual(-1, CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(-1)));
+ Assert.AreEqual(14927, CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(14927)));
+ Assert.AreEqual(-3612, CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(-3612)));
+
+ Assert.AreEqual(856912304801416L,
+ CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(856912304801416L)));
+ Assert.AreEqual(-75123905439571256L,
+ CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(-75123905439571256L)));
+ }
+
+ [Test]
+ public void TestNegativeEnumNoTag()
+ {
+ Assert.AreEqual(10, CodedOutputStream.ComputeInt32Size(-2));
+ Assert.AreEqual(10, CodedOutputStream.ComputeEnumSize((int) SampleEnum.NegativeValue));
+
+ byte[] bytes = new byte[10];
+ CodedOutputStream output = new CodedOutputStream(bytes);
+ output.WriteEnum((int) SampleEnum.NegativeValue);
+
+ Assert.AreEqual(0, output.SpaceLeft);
+ Assert.AreEqual("FE-FF-FF-FF-FF-FF-FF-FF-FF-01", BitConverter.ToString(bytes));
+ }
+
+ [Test]
+ public void TestCodedInputOutputPosition()
+ {
+ byte[] content = new byte[110];
+ for (int i = 0; i < content.Length; i++)
+ content[i] = (byte)i;
+
+ byte[] child = new byte[120];
+ {
+ MemoryStream ms = new MemoryStream(child);
+ CodedOutputStream cout = new CodedOutputStream(ms, 20);
+ // Field 11: numeric value: 500
+ cout.WriteTag(11, WireFormat.WireType.Varint);
+ Assert.AreEqual(1, cout.Position);
+ cout.WriteInt32(500);
+ Assert.AreEqual(3, cout.Position);
+ //Field 12: length delimited 120 bytes
+ cout.WriteTag(12, WireFormat.WireType.LengthDelimited);
+ Assert.AreEqual(4, cout.Position);
+ cout.WriteBytes(ByteString.CopyFrom(content));
+ Assert.AreEqual(115, cout.Position);
+ // Field 13: fixed numeric value: 501
+ cout.WriteTag(13, WireFormat.WireType.Fixed32);
+ Assert.AreEqual(116, cout.Position);
+ cout.WriteSFixed32(501);
+ Assert.AreEqual(120, cout.Position);
+ cout.Flush();
+ }
+
+ byte[] bytes = new byte[130];
+ {
+ CodedOutputStream cout = new CodedOutputStream(bytes);
+ // Field 1: numeric value: 500
+ cout.WriteTag(1, WireFormat.WireType.Varint);
+ Assert.AreEqual(1, cout.Position);
+ cout.WriteInt32(500);
+ Assert.AreEqual(3, cout.Position);
+ //Field 2: length delimited 120 bytes
+ cout.WriteTag(2, WireFormat.WireType.LengthDelimited);
+ Assert.AreEqual(4, cout.Position);
+ cout.WriteBytes(ByteString.CopyFrom(child));
+ Assert.AreEqual(125, cout.Position);
+ // Field 3: fixed numeric value: 500
+ cout.WriteTag(3, WireFormat.WireType.Fixed32);
+ Assert.AreEqual(126, cout.Position);
+ cout.WriteSFixed32(501);
+ Assert.AreEqual(130, cout.Position);
+ cout.Flush();
+ }
+ // Now test Input stream:
+ {
+ CodedInputStream cin = new CodedInputStream(new MemoryStream(bytes), new byte[50], 0, 0);
+ Assert.AreEqual(0, cin.Position);
+ // Field 1:
+ uint tag = cin.ReadTag();
+ Assert.AreEqual(1, tag >> 3);
+ Assert.AreEqual(1, cin.Position);
+ Assert.AreEqual(500, cin.ReadInt32());
+ Assert.AreEqual(3, cin.Position);
+ //Field 2:
+ tag = cin.ReadTag();
+ Assert.AreEqual(2, tag >> 3);
+ Assert.AreEqual(4, cin.Position);
+ int childlen = cin.ReadLength();
+ Assert.AreEqual(120, childlen);
+ Assert.AreEqual(5, cin.Position);
+ int oldlimit = cin.PushLimit((int)childlen);
+ Assert.AreEqual(5, cin.Position);
+ // Now we are reading child message
+ {
+ // Field 11: numeric value: 500
+ tag = cin.ReadTag();
+ Assert.AreEqual(11, tag >> 3);
+ Assert.AreEqual(6, cin.Position);
+ Assert.AreEqual(500, cin.ReadInt32());
+ Assert.AreEqual(8, cin.Position);
+ //Field 12: length delimited 120 bytes
+ tag = cin.ReadTag();
+ Assert.AreEqual(12, tag >> 3);
+ Assert.AreEqual(9, cin.Position);
+ ByteString bstr = cin.ReadBytes();
+ Assert.AreEqual(110, bstr.Length);
+ Assert.AreEqual((byte) 109, bstr[109]);
+ Assert.AreEqual(120, cin.Position);
+ // Field 13: fixed numeric value: 501
+ tag = cin.ReadTag();
+ Assert.AreEqual(13, tag >> 3);
+ // ROK - Previously broken here, this returned 126 failing to account for bufferSizeAfterLimit
+ Assert.AreEqual(121, cin.Position);
+ Assert.AreEqual(501, cin.ReadSFixed32());
+ Assert.AreEqual(125, cin.Position);
+ Assert.IsTrue(cin.IsAtEnd);
+ }
+ cin.PopLimit(oldlimit);
+ Assert.AreEqual(125, cin.Position);
+ // Field 3: fixed numeric value: 501
+ tag = cin.ReadTag();
+ Assert.AreEqual(3, tag >> 3);
+ Assert.AreEqual(126, cin.Position);
+ Assert.AreEqual(501, cin.ReadSFixed32());
+ Assert.AreEqual(130, cin.Position);
+ Assert.IsTrue(cin.IsAtEnd);
+ }
+ }
+
+ [Test]
+ public void Dispose_DisposesUnderlyingStream()
+ {
+ var memoryStream = new MemoryStream();
+ Assert.IsTrue(memoryStream.CanWrite);
+ using (var cos = new CodedOutputStream(memoryStream))
+ {
+ cos.WriteRawByte(0);
+ Assert.AreEqual(0, memoryStream.Position); // Not flushed yet
+ }
+ Assert.AreEqual(1, memoryStream.ToArray().Length); // Flushed data from CodedOutputStream to MemoryStream
+ Assert.IsFalse(memoryStream.CanWrite); // Disposed
+ }
+
+ [Test]
+ public void Dispose_WithLeaveOpen()
+ {
+ var memoryStream = new MemoryStream();
+ Assert.IsTrue(memoryStream.CanWrite);
+ using (var cos = new CodedOutputStream(memoryStream, true))
+ {
+ cos.WriteRawByte(0);
+ Assert.AreEqual(0, memoryStream.Position); // Not flushed yet
+ }
+ Assert.AreEqual(1, memoryStream.Position); // Flushed data from CodedOutputStream to MemoryStream
+ Assert.IsTrue(memoryStream.CanWrite); // We left the stream open
+ }
+ }
+} \ No newline at end of file
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Collections/MapFieldTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Collections/MapFieldTest.cs
new file mode 100644
index 00000000..9c845907
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Collections/MapFieldTest.cs
@@ -0,0 +1,532 @@
+#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 System;
+using System.Collections.Generic;
+using Google.Protobuf.TestProtos;
+using NUnit.Framework;
+using System.Collections;
+using System.Linq;
+
+namespace Google.Protobuf.Collections
+{
+ /// <summary>
+ /// Tests for MapField which aren't reliant on the encoded format -
+ /// tests for serialization/deserialization are part of GeneratedMessageTest.
+ /// </summary>
+ public class MapFieldTest
+ {
+ [Test]
+ public void Clone_ClonesMessages()
+ {
+ var message = new ForeignMessage { C = 20 };
+ var map = new MapField<string, ForeignMessage> { { "x", message } };
+ var clone = map.Clone();
+ map["x"].C = 30;
+ Assert.AreEqual(20, clone["x"].C);
+ }
+
+ [Test]
+ public void NullValuesProhibited()
+ {
+ TestNullValues<int?>(0);
+ TestNullValues("");
+ TestNullValues(new TestAllTypes());
+ }
+
+ private void TestNullValues<T>(T nonNullValue)
+ {
+ var map = new MapField<int, T>();
+ var nullValue = (T) (object) null;
+ Assert.Throws<ArgumentNullException>(() => map.Add(0, nullValue));
+ Assert.Throws<ArgumentNullException>(() => map[0] = nullValue);
+ map.Add(1, nonNullValue);
+ map[1] = nonNullValue;
+ }
+
+ [Test]
+ public void Add_ForbidsNullKeys()
+ {
+ var map = new MapField<string, ForeignMessage>();
+ Assert.Throws<ArgumentNullException>(() => map.Add(null, new ForeignMessage()));
+ }
+
+ [Test]
+ public void Indexer_ForbidsNullKeys()
+ {
+ var map = new MapField<string, ForeignMessage>();
+ Assert.Throws<ArgumentNullException>(() => map[null] = new ForeignMessage());
+ }
+
+ [Test]
+ public void AddPreservesInsertionOrder()
+ {
+ var map = new MapField<string, string>();
+ map.Add("a", "v1");
+ map.Add("b", "v2");
+ map.Add("c", "v3");
+ map.Remove("b");
+ map.Add("d", "v4");
+ CollectionAssert.AreEqual(new[] { "a", "c", "d" }, map.Keys);
+ CollectionAssert.AreEqual(new[] { "v1", "v3", "v4" }, map.Values);
+ }
+
+ [Test]
+ public void EqualityIsOrderInsensitive()
+ {
+ var map1 = new MapField<string, string>();
+ map1.Add("a", "v1");
+ map1.Add("b", "v2");
+
+ var map2 = new MapField<string, string>();
+ map2.Add("b", "v2");
+ map2.Add("a", "v1");
+
+ EqualityTester.AssertEquality(map1, map2);
+ }
+
+ [Test]
+ public void EqualityIsKeySensitive()
+ {
+ var map1 = new MapField<string, string>();
+ map1.Add("first key", "v1");
+ map1.Add("second key", "v2");
+
+ var map2 = new MapField<string, string>();
+ map2.Add("third key", "v1");
+ map2.Add("fourth key", "v2");
+
+ EqualityTester.AssertInequality(map1, map2);
+ }
+
+ [Test]
+ public void Equality_Simple()
+ {
+ var map = new MapField<string, string>();
+ EqualityTester.AssertEquality(map, map);
+ EqualityTester.AssertInequality(map, null);
+ Assert.IsFalse(map.Equals(new object()));
+ }
+
+ [Test]
+ public void EqualityIsValueSensitive()
+ {
+ // Note: Without some care, it's a little easier than one might
+ // hope to see hash collisions, but only in some environments...
+ var map1 = new MapField<string, string>();
+ map1.Add("a", "first value");
+ map1.Add("b", "second value");
+
+ var map2 = new MapField<string, string>();
+ map2.Add("a", "third value");
+ map2.Add("b", "fourth value");
+
+ EqualityTester.AssertInequality(map1, map2);
+ }
+
+ [Test]
+ public void Add_Dictionary()
+ {
+ var map1 = new MapField<string, string>
+ {
+ { "x", "y" },
+ { "a", "b" }
+ };
+ var map2 = new MapField<string, string>
+ {
+ { "before", "" },
+ map1,
+ { "after", "" }
+ };
+ var expected = new MapField<string, string>
+ {
+ { "before", "" },
+ { "x", "y" },
+ { "a", "b" },
+ { "after", "" }
+ };
+ Assert.AreEqual(expected, map2);
+ CollectionAssert.AreEqual(new[] { "before", "x", "a", "after" }, map2.Keys);
+ }
+
+ // General IDictionary<TKey, TValue> behavior tests
+ [Test]
+ public void Add_KeyAlreadyExists()
+ {
+ var map = new MapField<string, string>();
+ map.Add("foo", "bar");
+ Assert.Throws<ArgumentException>(() => map.Add("foo", "baz"));
+ }
+
+ [Test]
+ public void Add_Pair()
+ {
+ var map = new MapField<string, string>();
+ ICollection<KeyValuePair<string, string>> collection = map;
+ collection.Add(NewKeyValuePair("x", "y"));
+ Assert.AreEqual("y", map["x"]);
+ Assert.Throws<ArgumentException>(() => collection.Add(NewKeyValuePair("x", "z")));
+ }
+
+ [Test]
+ public void Contains_Pair()
+ {
+ var map = new MapField<string, string> { { "x", "y" } };
+ ICollection<KeyValuePair<string, string>> collection = map;
+ Assert.IsTrue(collection.Contains(NewKeyValuePair("x", "y")));
+ Assert.IsFalse(collection.Contains(NewKeyValuePair("x", "z")));
+ Assert.IsFalse(collection.Contains(NewKeyValuePair("z", "y")));
+ }
+
+ [Test]
+ public void Remove_Key()
+ {
+ var map = new MapField<string, string>();
+ map.Add("foo", "bar");
+ Assert.AreEqual(1, map.Count);
+ Assert.IsFalse(map.Remove("missing"));
+ Assert.AreEqual(1, map.Count);
+ Assert.IsTrue(map.Remove("foo"));
+ Assert.AreEqual(0, map.Count);
+ Assert.Throws<ArgumentNullException>(() => map.Remove(null));
+ }
+
+ [Test]
+ public void Remove_Pair()
+ {
+ var map = new MapField<string, string>();
+ map.Add("foo", "bar");
+ ICollection<KeyValuePair<string, string>> collection = map;
+ Assert.AreEqual(1, map.Count);
+ Assert.IsFalse(collection.Remove(NewKeyValuePair("wrong key", "bar")));
+ Assert.AreEqual(1, map.Count);
+ Assert.IsFalse(collection.Remove(NewKeyValuePair("foo", "wrong value")));
+ Assert.AreEqual(1, map.Count);
+ Assert.IsTrue(collection.Remove(NewKeyValuePair("foo", "bar")));
+ Assert.AreEqual(0, map.Count);
+ Assert.Throws<ArgumentException>(() => collection.Remove(new KeyValuePair<string, string>(null, "")));
+ }
+
+ [Test]
+ public void CopyTo_Pair()
+ {
+ var map = new MapField<string, string>();
+ map.Add("foo", "bar");
+ ICollection<KeyValuePair<string, string>> collection = map;
+ KeyValuePair<string, string>[] array = new KeyValuePair<string, string>[3];
+ collection.CopyTo(array, 1);
+ Assert.AreEqual(NewKeyValuePair("foo", "bar"), array[1]);
+ }
+
+ [Test]
+ public void Clear()
+ {
+ var map = new MapField<string, string> { { "x", "y" } };
+ Assert.AreEqual(1, map.Count);
+ map.Clear();
+ Assert.AreEqual(0, map.Count);
+ map.Add("x", "y");
+ Assert.AreEqual(1, map.Count);
+ }
+
+ [Test]
+ public void Indexer_Get()
+ {
+ var map = new MapField<string, string> { { "x", "y" } };
+ Assert.AreEqual("y", map["x"]);
+ Assert.Throws<KeyNotFoundException>(() => { var ignored = map["z"]; });
+ }
+
+ [Test]
+ public void Indexer_Set()
+ {
+ var map = new MapField<string, string>();
+ map["x"] = "y";
+ Assert.AreEqual("y", map["x"]);
+ map["x"] = "z"; // This won't throw, unlike Add.
+ Assert.AreEqual("z", map["x"]);
+ }
+
+ [Test]
+ public void GetEnumerator_NonGeneric()
+ {
+ IEnumerable map = new MapField<string, string> { { "x", "y" } };
+ CollectionAssert.AreEqual(new[] { new KeyValuePair<string, string>("x", "y") },
+ map.Cast<object>().ToList());
+ }
+
+ // Test for the explicitly-implemented non-generic IDictionary interface
+ [Test]
+ public void IDictionary_GetEnumerator()
+ {
+ IDictionary map = new MapField<string, string> { { "x", "y" } };
+ var enumerator = map.GetEnumerator();
+
+ // Commented assertions show an ideal situation - it looks like
+ // the LinkedList enumerator doesn't throw when you ask for the current entry
+ // at an inappropriate time; fixing this would be more work than it's worth.
+ // Assert.Throws<InvalidOperationException>(() => enumerator.Current.GetHashCode());
+ Assert.IsTrue(enumerator.MoveNext());
+ Assert.AreEqual("x", enumerator.Key);
+ Assert.AreEqual("y", enumerator.Value);
+ Assert.AreEqual(new DictionaryEntry("x", "y"), enumerator.Current);
+ Assert.AreEqual(new DictionaryEntry("x", "y"), enumerator.Entry);
+ Assert.IsFalse(enumerator.MoveNext());
+ // Assert.Throws<InvalidOperationException>(() => enumerator.Current.GetHashCode());
+ enumerator.Reset();
+ // Assert.Throws<InvalidOperationException>(() => enumerator.Current.GetHashCode());
+ Assert.IsTrue(enumerator.MoveNext());
+ Assert.AreEqual("x", enumerator.Key); // Assume the rest are okay
+ }
+
+ [Test]
+ public void IDictionary_Add()
+ {
+ var map = new MapField<string, string> { { "x", "y" } };
+ IDictionary dictionary = map;
+ dictionary.Add("a", "b");
+ Assert.AreEqual("b", map["a"]);
+ Assert.Throws<ArgumentException>(() => dictionary.Add("a", "duplicate"));
+ Assert.Throws<InvalidCastException>(() => dictionary.Add(new object(), "key is bad"));
+ Assert.Throws<InvalidCastException>(() => dictionary.Add("value is bad", new object()));
+ }
+
+ [Test]
+ public void IDictionary_Contains()
+ {
+ var map = new MapField<string, string> { { "x", "y" } };
+ IDictionary dictionary = map;
+
+ Assert.IsFalse(dictionary.Contains("a"));
+ Assert.IsFalse(dictionary.Contains(5));
+ // Surprising, but IDictionary.Contains is only about keys.
+ Assert.IsFalse(dictionary.Contains(new DictionaryEntry("x", "y")));
+ Assert.IsTrue(dictionary.Contains("x"));
+ }
+
+ [Test]
+ public void IDictionary_Remove()
+ {
+ var map = new MapField<string, string> { { "x", "y" } };
+ IDictionary dictionary = map;
+ dictionary.Remove("a");
+ Assert.AreEqual(1, dictionary.Count);
+ dictionary.Remove(5);
+ Assert.AreEqual(1, dictionary.Count);
+ dictionary.Remove(new DictionaryEntry("x", "y"));
+ Assert.AreEqual(1, dictionary.Count);
+ dictionary.Remove("x");
+ Assert.AreEqual(0, dictionary.Count);
+ Assert.Throws<ArgumentNullException>(() => dictionary.Remove(null));
+ }
+
+ [Test]
+ public void IDictionary_CopyTo()
+ {
+ var map = new MapField<string, string> { { "x", "y" } };
+ IDictionary dictionary = map;
+ var array = new DictionaryEntry[3];
+ dictionary.CopyTo(array, 1);
+ CollectionAssert.AreEqual(new[] { default(DictionaryEntry), new DictionaryEntry("x", "y"), default(DictionaryEntry) },
+ array);
+ var objectArray = new object[3];
+ dictionary.CopyTo(objectArray, 1);
+ CollectionAssert.AreEqual(new object[] { null, new DictionaryEntry("x", "y"), null },
+ objectArray);
+ }
+
+ [Test]
+ public void IDictionary_IsFixedSize()
+ {
+ var map = new MapField<string, string> { { "x", "y" } };
+ IDictionary dictionary = map;
+ Assert.IsFalse(dictionary.IsFixedSize);
+ }
+
+ [Test]
+ public void IDictionary_Keys()
+ {
+ IDictionary dictionary = new MapField<string, string> { { "x", "y" } };
+ CollectionAssert.AreEqual(new[] { "x" }, dictionary.Keys);
+ }
+
+ [Test]
+ public void IDictionary_Values()
+ {
+ IDictionary dictionary = new MapField<string, string> { { "x", "y" } };
+ CollectionAssert.AreEqual(new[] { "y" }, dictionary.Values);
+ }
+
+ [Test]
+ public void IDictionary_IsSynchronized()
+ {
+ IDictionary dictionary = new MapField<string, string> { { "x", "y" } };
+ Assert.IsFalse(dictionary.IsSynchronized);
+ }
+
+ [Test]
+ public void IDictionary_SyncRoot()
+ {
+ IDictionary dictionary = new MapField<string, string> { { "x", "y" } };
+ Assert.AreSame(dictionary, dictionary.SyncRoot);
+ }
+
+ [Test]
+ public void IDictionary_Indexer_Get()
+ {
+ IDictionary dictionary = new MapField<string, string> { { "x", "y" } };
+ Assert.AreEqual("y", dictionary["x"]);
+ Assert.IsNull(dictionary["a"]);
+ Assert.IsNull(dictionary[5]);
+ Assert.Throws<ArgumentNullException>(() => dictionary[null].GetHashCode());
+ }
+
+ [Test]
+ public void IDictionary_Indexer_Set()
+ {
+ var map = new MapField<string, string> { { "x", "y" } };
+ IDictionary dictionary = map;
+ map["a"] = "b";
+ Assert.AreEqual("b", map["a"]);
+ map["a"] = "c";
+ Assert.AreEqual("c", map["a"]);
+ Assert.Throws<InvalidCastException>(() => dictionary[5] = "x");
+ Assert.Throws<InvalidCastException>(() => dictionary["x"] = 5);
+ Assert.Throws<ArgumentNullException>(() => dictionary[null] = "z");
+ Assert.Throws<ArgumentNullException>(() => dictionary["x"] = null);
+ }
+
+ [Test]
+ public void KeysReturnsLiveView()
+ {
+ var map = new MapField<string, string>();
+ var keys = map.Keys;
+ CollectionAssert.AreEqual(new string[0], keys);
+ map["foo"] = "bar";
+ map["x"] = "y";
+ CollectionAssert.AreEqual(new[] { "foo", "x" }, keys);
+ }
+
+ [Test]
+ public void ValuesReturnsLiveView()
+ {
+ var map = new MapField<string, string>();
+ var values = map.Values;
+ CollectionAssert.AreEqual(new string[0], values);
+ map["foo"] = "bar";
+ map["x"] = "y";
+ CollectionAssert.AreEqual(new[] { "bar", "y" }, values);
+ }
+
+ // Just test keys - we know the implementation is the same for values
+ [Test]
+ public void ViewsAreReadOnly()
+ {
+ var map = new MapField<string, string>();
+ var keys = map.Keys;
+ Assert.IsTrue(keys.IsReadOnly);
+ Assert.Throws<NotSupportedException>(() => keys.Clear());
+ Assert.Throws<NotSupportedException>(() => keys.Remove("a"));
+ Assert.Throws<NotSupportedException>(() => keys.Add("a"));
+ }
+
+ // Just test keys - we know the implementation is the same for values
+ [Test]
+ public void ViewCopyTo()
+ {
+ var map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } };
+ var keys = map.Keys;
+ var array = new string[4];
+ Assert.Throws<ArgumentException>(() => keys.CopyTo(array, 3));
+ Assert.Throws<ArgumentOutOfRangeException>(() => keys.CopyTo(array, -1));
+ keys.CopyTo(array, 1);
+ CollectionAssert.AreEqual(new[] { null, "foo", "x", null }, array);
+ }
+
+ // Just test keys - we know the implementation is the same for values
+ [Test]
+ public void NonGenericViewCopyTo()
+ {
+ IDictionary map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } };
+ ICollection keys = map.Keys;
+ // Note the use of the Array type here rather than string[]
+ Array array = new string[4];
+ Assert.Throws<ArgumentException>(() => keys.CopyTo(array, 3));
+ Assert.Throws<ArgumentOutOfRangeException>(() => keys.CopyTo(array, -1));
+ keys.CopyTo(array, 1);
+ CollectionAssert.AreEqual(new[] { null, "foo", "x", null }, array);
+ }
+
+ [Test]
+ public void KeysContains()
+ {
+ var map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } };
+ var keys = map.Keys;
+ Assert.IsTrue(keys.Contains("foo"));
+ Assert.IsFalse(keys.Contains("bar")); // It's a value!
+ Assert.IsFalse(keys.Contains("1"));
+ // Keys can't be null, so we should prevent contains check
+ Assert.Throws<ArgumentNullException>(() => keys.Contains(null));
+ }
+
+ [Test]
+ public void ValuesContains()
+ {
+ var map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } };
+ var values = map.Values;
+ Assert.IsTrue(values.Contains("bar"));
+ Assert.IsFalse(values.Contains("foo")); // It's a key!
+ Assert.IsFalse(values.Contains("1"));
+ // Values can be null, so this makes sense
+ Assert.IsFalse(values.Contains(null));
+ }
+
+ [Test]
+ public void ToString_StringToString()
+ {
+ var map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } };
+ Assert.AreEqual("{ \"foo\": \"bar\", \"x\": \"y\" }", map.ToString());
+ }
+
+ [Test]
+ public void ToString_UnsupportedKeyType()
+ {
+ var map = new MapField<byte, string> { { 10, "foo" } };
+ Assert.Throws<ArgumentException>(() => map.ToString());
+ }
+
+ private static KeyValuePair<TKey, TValue> NewKeyValuePair<TKey, TValue>(TKey key, TValue value)
+ {
+ return new KeyValuePair<TKey, TValue>(key, value);
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs
new file mode 100644
index 00000000..6852f75f
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs
@@ -0,0 +1,746 @@
+#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 System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Google.Protobuf.TestProtos;
+using Google.Protobuf.WellKnownTypes;
+using NUnit.Framework;
+
+namespace Google.Protobuf.Collections
+{
+ public class RepeatedFieldTest
+ {
+ [Test]
+ public void NullValuesRejected()
+ {
+ var list = new RepeatedField<string>();
+ Assert.Throws<ArgumentNullException>(() => list.Add((string)null));
+ Assert.Throws<ArgumentNullException>(() => list.Add((IEnumerable<string>)null));
+ Assert.Throws<ArgumentNullException>(() => list.Add((RepeatedField<string>)null));
+ Assert.Throws<ArgumentNullException>(() => list.Contains(null));
+ Assert.Throws<ArgumentNullException>(() => list.IndexOf(null));
+ }
+
+ [Test]
+ public void Add_SingleItem()
+ {
+ var list = new RepeatedField<string>();
+ list.Add("foo");
+ Assert.AreEqual(1, list.Count);
+ Assert.AreEqual("foo", list[0]);
+ }
+
+ [Test]
+ public void Add_Sequence()
+ {
+ var list = new RepeatedField<string>();
+ list.Add(new[] { "foo", "bar" });
+ Assert.AreEqual(2, list.Count);
+ Assert.AreEqual("foo", list[0]);
+ Assert.AreEqual("bar", list[1]);
+ }
+
+ [Test]
+ public void AddRange_SlowPath()
+ {
+ var list = new RepeatedField<string>();
+ list.AddRange(new[] { "foo", "bar" }.Select(x => x));
+ Assert.AreEqual(2, list.Count);
+ Assert.AreEqual("foo", list[0]);
+ Assert.AreEqual("bar", list[1]);
+ }
+
+ [Test]
+ public void AddRange_SlowPath_NullsProhibited_ReferenceType()
+ {
+ var list = new RepeatedField<string>();
+ // It's okay for this to throw ArgumentNullException if necessary.
+ // It's not ideal, but not awful.
+ Assert.Catch<ArgumentException>(() => list.AddRange(new[] { "foo", null }.Select(x => x)));
+ }
+
+ [Test]
+ public void AddRange_SlowPath_NullsProhibited_NullableValueType()
+ {
+ var list = new RepeatedField<int?>();
+ // It's okay for this to throw ArgumentNullException if necessary.
+ // It's not ideal, but not awful.
+ Assert.Catch<ArgumentException>(() => list.AddRange(new[] { 20, (int?)null }.Select(x => x)));
+ }
+
+ [Test]
+ public void AddRange_Optimized_NonNullableValueType()
+ {
+ var list = new RepeatedField<int>();
+ list.AddRange(new List<int> { 20, 30 });
+ Assert.AreEqual(2, list.Count);
+ Assert.AreEqual(20, list[0]);
+ Assert.AreEqual(30, list[1]);
+ }
+
+ [Test]
+ public void AddRange_Optimized_ReferenceType()
+ {
+ var list = new RepeatedField<string>();
+ list.AddRange(new List<string> { "foo", "bar" });
+ Assert.AreEqual(2, list.Count);
+ Assert.AreEqual("foo", list[0]);
+ Assert.AreEqual("bar", list[1]);
+ }
+
+ [Test]
+ public void AddRange_Optimized_NullableValueType()
+ {
+ var list = new RepeatedField<int?>();
+ list.AddRange(new List<int?> { 20, 30 });
+ Assert.AreEqual(2, list.Count);
+ Assert.AreEqual((int?) 20, list[0]);
+ Assert.AreEqual((int?) 30, list[1]);
+ }
+
+ [Test]
+ public void AddRange_Optimized_NullsProhibited_ReferenceType()
+ {
+ // We don't just trust that a collection with a nullable element type doesn't contain nulls
+ var list = new RepeatedField<string>();
+ // It's okay for this to throw ArgumentNullException if necessary.
+ // It's not ideal, but not awful.
+ Assert.Catch<ArgumentException>(() => list.AddRange(new List<string> { "foo", null }));
+ }
+
+ [Test]
+ public void AddRange_Optimized_NullsProhibited_NullableValueType()
+ {
+ // We don't just trust that a collection with a nullable element type doesn't contain nulls
+ var list = new RepeatedField<int?>();
+ // It's okay for this to throw ArgumentNullException if necessary.
+ // It's not ideal, but not awful.
+ Assert.Catch<ArgumentException>(() => list.AddRange(new List<int?> { 20, null }));
+ }
+
+ [Test]
+ public void AddRange_AlreadyNotEmpty()
+ {
+ var list = new RepeatedField<int> { 1, 2, 3 };
+ list.AddRange(new List<int> { 4, 5, 6 });
+ CollectionAssert.AreEqual(new[] { 1, 2, 3, 4, 5, 6 }, list);
+ }
+
+ [Test]
+ public void AddRange_RepeatedField()
+ {
+ var list = new RepeatedField<string> { "original" };
+ list.AddRange(new RepeatedField<string> { "foo", "bar" });
+ Assert.AreEqual(3, list.Count);
+ Assert.AreEqual("original", list[0]);
+ Assert.AreEqual("foo", list[1]);
+ Assert.AreEqual("bar", list[2]);
+ }
+
+ [Test]
+ public void RemoveAt_Valid()
+ {
+ var list = new RepeatedField<string> { "first", "second", "third" };
+ list.RemoveAt(1);
+ CollectionAssert.AreEqual(new[] { "first", "third" }, list);
+ // Just check that these don't throw...
+ list.RemoveAt(list.Count - 1); // Now the count will be 1...
+ list.RemoveAt(0);
+ Assert.AreEqual(0, list.Count);
+ }
+
+ [Test]
+ public void RemoveAt_Invalid()
+ {
+ var list = new RepeatedField<string> { "first", "second", "third" };
+ Assert.Throws<ArgumentOutOfRangeException>(() => list.RemoveAt(-1));
+ Assert.Throws<ArgumentOutOfRangeException>(() => list.RemoveAt(3));
+ }
+
+ [Test]
+ public void Insert_Valid()
+ {
+ var list = new RepeatedField<string> { "first", "second" };
+ list.Insert(1, "middle");
+ CollectionAssert.AreEqual(new[] { "first", "middle", "second" }, list);
+ list.Insert(3, "end");
+ CollectionAssert.AreEqual(new[] { "first", "middle", "second", "end" }, list);
+ list.Insert(0, "start");
+ CollectionAssert.AreEqual(new[] { "start", "first", "middle", "second", "end" }, list);
+ }
+
+ [Test]
+ public void Insert_Invalid()
+ {
+ var list = new RepeatedField<string> { "first", "second" };
+ Assert.Throws<ArgumentOutOfRangeException>(() => list.Insert(-1, "foo"));
+ Assert.Throws<ArgumentOutOfRangeException>(() => list.Insert(3, "foo"));
+ Assert.Throws<ArgumentNullException>(() => list.Insert(0, null));
+ }
+
+ [Test]
+ public void Equals_RepeatedField()
+ {
+ var list = new RepeatedField<string> { "first", "second" };
+ Assert.IsFalse(list.Equals((RepeatedField<string>) null));
+ Assert.IsTrue(list.Equals(list));
+ Assert.IsFalse(list.Equals(new RepeatedField<string> { "first", "third" }));
+ Assert.IsFalse(list.Equals(new RepeatedField<string> { "first" }));
+ Assert.IsTrue(list.Equals(new RepeatedField<string> { "first", "second" }));
+ }
+
+ [Test]
+ public void Equals_Object()
+ {
+ var list = new RepeatedField<string> { "first", "second" };
+ Assert.IsFalse(list.Equals((object) null));
+ Assert.IsTrue(list.Equals((object) list));
+ Assert.IsFalse(list.Equals((object) new RepeatedField<string> { "first", "third" }));
+ Assert.IsFalse(list.Equals((object) new RepeatedField<string> { "first" }));
+ Assert.IsTrue(list.Equals((object) new RepeatedField<string> { "first", "second" }));
+ Assert.IsFalse(list.Equals(new object()));
+ }
+
+ [Test]
+ public void GetEnumerator_GenericInterface()
+ {
+ IEnumerable<string> list = new RepeatedField<string> { "first", "second" };
+ // Select gets rid of the optimizations in ToList...
+ CollectionAssert.AreEqual(new[] { "first", "second" }, list.Select(x => x).ToList());
+ }
+
+ [Test]
+ public void GetEnumerator_NonGenericInterface()
+ {
+ IEnumerable list = new RepeatedField<string> { "first", "second" };
+ CollectionAssert.AreEqual(new[] { "first", "second" }, list.Cast<object>().ToList());
+ }
+
+ [Test]
+ public void CopyTo()
+ {
+ var list = new RepeatedField<string> { "first", "second" };
+ string[] stringArray = new string[4];
+ list.CopyTo(stringArray, 1);
+ CollectionAssert.AreEqual(new[] { null, "first", "second", null }, stringArray);
+ }
+
+ [Test]
+ public void Indexer_Get()
+ {
+ var list = new RepeatedField<string> { "first", "second" };
+ Assert.AreEqual("first", list[0]);
+ Assert.AreEqual("second", list[1]);
+ Assert.Throws<ArgumentOutOfRangeException>(() => list[-1].GetHashCode());
+ Assert.Throws<ArgumentOutOfRangeException>(() => list[2].GetHashCode());
+ }
+
+ [Test]
+ public void Indexer_Set()
+ {
+ var list = new RepeatedField<string> { "first", "second" };
+ list[0] = "changed";
+ Assert.AreEqual("changed", list[0]);
+ Assert.Throws<ArgumentNullException>(() => list[0] = null);
+ Assert.Throws<ArgumentOutOfRangeException>(() => list[-1] = "bad");
+ Assert.Throws<ArgumentOutOfRangeException>(() => list[2] = "bad");
+ }
+
+ [Test]
+ public void Clone_ReturnsMutable()
+ {
+ var list = new RepeatedField<int> { 0 };
+ var clone = list.Clone();
+ clone[0] = 1;
+ }
+
+ [Test]
+ public void Enumerator()
+ {
+ var list = new RepeatedField<string> { "first", "second" };
+ using (var enumerator = list.GetEnumerator())
+ {
+ Assert.IsTrue(enumerator.MoveNext());
+ Assert.AreEqual("first", enumerator.Current);
+ Assert.IsTrue(enumerator.MoveNext());
+ Assert.AreEqual("second", enumerator.Current);
+ Assert.IsFalse(enumerator.MoveNext());
+ Assert.IsFalse(enumerator.MoveNext());
+ }
+ }
+
+ [Test]
+ public void AddEntriesFrom_PackedInt32()
+ {
+ uint packedTag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited);
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ var length = CodedOutputStream.ComputeInt32Size(10)
+ + CodedOutputStream.ComputeInt32Size(999)
+ + CodedOutputStream.ComputeInt32Size(-1000);
+ output.WriteTag(packedTag);
+ output.WriteRawVarint32((uint) length);
+ output.WriteInt32(10);
+ output.WriteInt32(999);
+ output.WriteInt32(-1000);
+ output.Flush();
+ stream.Position = 0;
+
+ // Deliberately "expecting" a non-packed tag, but we detect that the data is
+ // actually packed.
+ uint nonPackedTag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited);
+ var field = new RepeatedField<int>();
+ var input = new CodedInputStream(stream);
+ input.AssertNextTag(packedTag);
+ field.AddEntriesFrom(input, FieldCodec.ForInt32(nonPackedTag));
+ CollectionAssert.AreEqual(new[] { 10, 999, -1000 }, field);
+ Assert.IsTrue(input.IsAtEnd);
+ }
+
+ [Test]
+ public void AddEntriesFrom_NonPackedInt32()
+ {
+ uint nonPackedTag = WireFormat.MakeTag(10, WireFormat.WireType.Varint);
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ output.WriteTag(nonPackedTag);
+ output.WriteInt32(10);
+ output.WriteTag(nonPackedTag);
+ output.WriteInt32(999);
+ output.WriteTag(nonPackedTag);
+ output.WriteInt32(-1000); // Just for variety...
+ output.Flush();
+ stream.Position = 0;
+
+ // Deliberately "expecting" a packed tag, but we detect that the data is
+ // actually not packed.
+ uint packedTag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited);
+ var field = new RepeatedField<int>();
+ var input = new CodedInputStream(stream);
+ input.AssertNextTag(nonPackedTag);
+ field.AddEntriesFrom(input, FieldCodec.ForInt32(packedTag));
+ CollectionAssert.AreEqual(new[] { 10, 999, -1000 }, field);
+ Assert.IsTrue(input.IsAtEnd);
+ }
+
+ [Test]
+ public void AddEntriesFrom_String()
+ {
+ uint tag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited);
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ output.WriteTag(tag);
+ output.WriteString("Foo");
+ output.WriteTag(tag);
+ output.WriteString("");
+ output.WriteTag(tag);
+ output.WriteString("Bar");
+ output.Flush();
+ stream.Position = 0;
+
+ var field = new RepeatedField<string>();
+ var input = new CodedInputStream(stream);
+ input.AssertNextTag(tag);
+ field.AddEntriesFrom(input, FieldCodec.ForString(tag));
+ CollectionAssert.AreEqual(new[] { "Foo", "", "Bar" }, field);
+ Assert.IsTrue(input.IsAtEnd);
+ }
+
+ [Test]
+ public void AddEntriesFrom_Message()
+ {
+ var message1 = new ForeignMessage { C = 2000 };
+ var message2 = new ForeignMessage { C = -250 };
+
+ uint tag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited);
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ output.WriteTag(tag);
+ output.WriteMessage(message1);
+ output.WriteTag(tag);
+ output.WriteMessage(message2);
+ output.Flush();
+ stream.Position = 0;
+
+ var field = new RepeatedField<ForeignMessage>();
+ var input = new CodedInputStream(stream);
+ input.AssertNextTag(tag);
+ field.AddEntriesFrom(input, FieldCodec.ForMessage(tag, ForeignMessage.Parser));
+ CollectionAssert.AreEqual(new[] { message1, message2}, field);
+ Assert.IsTrue(input.IsAtEnd);
+ }
+
+ [Test]
+ public void WriteTo_PackedInt32()
+ {
+ uint tag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited);
+ var field = new RepeatedField<int> { 10, 1000, 1000000 };
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ field.WriteTo(output, FieldCodec.ForInt32(tag));
+ output.Flush();
+ stream.Position = 0;
+
+ var input = new CodedInputStream(stream);
+ input.AssertNextTag(tag);
+ var length = input.ReadLength();
+ Assert.AreEqual(10, input.ReadInt32());
+ Assert.AreEqual(1000, input.ReadInt32());
+ Assert.AreEqual(1000000, input.ReadInt32());
+ Assert.IsTrue(input.IsAtEnd);
+ Assert.AreEqual(1 + CodedOutputStream.ComputeLengthSize(length) + length, stream.Length);
+ }
+
+ [Test]
+ public void WriteTo_NonPackedInt32()
+ {
+ uint tag = WireFormat.MakeTag(10, WireFormat.WireType.Varint);
+ var field = new RepeatedField<int> { 10, 1000, 1000000};
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ field.WriteTo(output, FieldCodec.ForInt32(tag));
+ output.Flush();
+ stream.Position = 0;
+
+ var input = new CodedInputStream(stream);
+ input.AssertNextTag(tag);
+ Assert.AreEqual(10, input.ReadInt32());
+ input.AssertNextTag(tag);
+ Assert.AreEqual(1000, input.ReadInt32());
+ input.AssertNextTag(tag);
+ Assert.AreEqual(1000000, input.ReadInt32());
+ Assert.IsTrue(input.IsAtEnd);
+ }
+
+ [Test]
+ public void WriteTo_String()
+ {
+ uint tag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited);
+ var field = new RepeatedField<string> { "Foo", "", "Bar" };
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ field.WriteTo(output, FieldCodec.ForString(tag));
+ output.Flush();
+ stream.Position = 0;
+
+ var input = new CodedInputStream(stream);
+ input.AssertNextTag(tag);
+ Assert.AreEqual("Foo", input.ReadString());
+ input.AssertNextTag(tag);
+ Assert.AreEqual("", input.ReadString());
+ input.AssertNextTag(tag);
+ Assert.AreEqual("Bar", input.ReadString());
+ Assert.IsTrue(input.IsAtEnd);
+ }
+
+ [Test]
+ public void WriteTo_Message()
+ {
+ var message1 = new ForeignMessage { C = 20 };
+ var message2 = new ForeignMessage { C = 25 };
+ uint tag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited);
+ var field = new RepeatedField<ForeignMessage> { message1, message2 };
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ field.WriteTo(output, FieldCodec.ForMessage(tag, ForeignMessage.Parser));
+ output.Flush();
+ stream.Position = 0;
+
+ var input = new CodedInputStream(stream);
+ input.AssertNextTag(tag);
+ Assert.AreEqual(message1, input.ReadMessage(ForeignMessage.Parser));
+ input.AssertNextTag(tag);
+ Assert.AreEqual(message2, input.ReadMessage(ForeignMessage.Parser));
+ Assert.IsTrue(input.IsAtEnd);
+ }
+
+ [Test]
+ public void CalculateSize_VariableSizeNonPacked()
+ {
+ var list = new RepeatedField<int> { 1, 500, 1 };
+ var tag = WireFormat.MakeTag(1, WireFormat.WireType.Varint);
+ // 2 bytes for the first entry, 3 bytes for the second, 2 bytes for the third
+ Assert.AreEqual(7, list.CalculateSize(FieldCodec.ForInt32(tag)));
+ }
+
+ [Test]
+ public void CalculateSize_FixedSizeNonPacked()
+ {
+ var list = new RepeatedField<int> { 1, 500, 1 };
+ var tag = WireFormat.MakeTag(1, WireFormat.WireType.Fixed32);
+ // 5 bytes for the each entry
+ Assert.AreEqual(15, list.CalculateSize(FieldCodec.ForSFixed32(tag)));
+ }
+
+ [Test]
+ public void CalculateSize_VariableSizePacked()
+ {
+ var list = new RepeatedField<int> { 1, 500, 1};
+ var tag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
+ // 1 byte for the tag, 1 byte for the length,
+ // 1 byte for the first entry, 2 bytes for the second, 1 byte for the third
+ Assert.AreEqual(6, list.CalculateSize(FieldCodec.ForInt32(tag)));
+ }
+
+ [Test]
+ public void CalculateSize_FixedSizePacked()
+ {
+ var list = new RepeatedField<int> { 1, 500, 1 };
+ var tag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
+ // 1 byte for the tag, 1 byte for the length, 4 bytes per entry
+ Assert.AreEqual(14, list.CalculateSize(FieldCodec.ForSFixed32(tag)));
+ }
+
+ [Test]
+ public void TestNegativeEnumArray()
+ {
+ int arraySize = 1 + 1 + (11 * 5);
+ int msgSize = arraySize;
+ byte[] bytes = new byte[msgSize];
+ CodedOutputStream output = new CodedOutputStream(bytes);
+ uint tag = WireFormat.MakeTag(8, WireFormat.WireType.Varint);
+ for (int i = 0; i >= -5; i--)
+ {
+ output.WriteTag(tag);
+ output.WriteEnum(i);
+ }
+
+ Assert.AreEqual(0, output.SpaceLeft);
+
+ CodedInputStream input = new CodedInputStream(bytes);
+ tag = input.ReadTag();
+
+ RepeatedField<SampleEnum> values = new RepeatedField<SampleEnum>();
+ values.AddEntriesFrom(input, FieldCodec.ForEnum(tag, x => (int)x, x => (SampleEnum)x));
+
+ Assert.AreEqual(6, values.Count);
+ Assert.AreEqual(SampleEnum.None, values[0]);
+ Assert.AreEqual(((SampleEnum)(-1)), values[1]);
+ Assert.AreEqual(SampleEnum.NegativeValue, values[2]);
+ Assert.AreEqual(((SampleEnum)(-3)), values[3]);
+ Assert.AreEqual(((SampleEnum)(-4)), values[4]);
+ Assert.AreEqual(((SampleEnum)(-5)), values[5]);
+ }
+
+
+ [Test]
+ public void TestNegativeEnumPackedArray()
+ {
+ int arraySize = 1 + (10 * 5);
+ int msgSize = 1 + 1 + arraySize;
+ byte[] bytes = new byte[msgSize];
+ CodedOutputStream output = new CodedOutputStream(bytes);
+ // Length-delimited to show we want the packed representation
+ uint tag = WireFormat.MakeTag(8, WireFormat.WireType.LengthDelimited);
+ output.WriteTag(tag);
+ int size = 0;
+ for (int i = 0; i >= -5; i--)
+ {
+ size += CodedOutputStream.ComputeEnumSize(i);
+ }
+ output.WriteRawVarint32((uint)size);
+ for (int i = 0; i >= -5; i--)
+ {
+ output.WriteEnum(i);
+ }
+ Assert.AreEqual(0, output.SpaceLeft);
+
+ CodedInputStream input = new CodedInputStream(bytes);
+ tag = input.ReadTag();
+
+ RepeatedField<SampleEnum> values = new RepeatedField<SampleEnum>();
+ values.AddEntriesFrom(input, FieldCodec.ForEnum(tag, x => (int)x, x => (SampleEnum)x));
+
+ Assert.AreEqual(6, values.Count);
+ Assert.AreEqual(SampleEnum.None, values[0]);
+ Assert.AreEqual(((SampleEnum)(-1)), values[1]);
+ Assert.AreEqual(SampleEnum.NegativeValue, values[2]);
+ Assert.AreEqual(((SampleEnum)(-3)), values[3]);
+ Assert.AreEqual(((SampleEnum)(-4)), values[4]);
+ Assert.AreEqual(((SampleEnum)(-5)), values[5]);
+ }
+
+ // Fairly perfunctory tests for the non-generic IList implementation
+ [Test]
+ public void IList_Indexer()
+ {
+ var field = new RepeatedField<string> { "first", "second" };
+ IList list = field;
+ Assert.AreEqual("first", list[0]);
+ list[1] = "changed";
+ Assert.AreEqual("changed", field[1]);
+ }
+
+ [Test]
+ public void IList_Contains()
+ {
+ IList list = new RepeatedField<string> { "first", "second" };
+ Assert.IsTrue(list.Contains("second"));
+ Assert.IsFalse(list.Contains("third"));
+ Assert.IsFalse(list.Contains(new object()));
+ }
+
+ [Test]
+ public void IList_Add()
+ {
+ IList list = new RepeatedField<string> { "first", "second" };
+ list.Add("third");
+ CollectionAssert.AreEqual(new[] { "first", "second", "third" }, list);
+ }
+
+ [Test]
+ public void IList_Remove()
+ {
+ IList list = new RepeatedField<string> { "first", "second" };
+ list.Remove("third"); // No-op, no exception
+ list.Remove(new object()); // No-op, no exception
+ list.Remove("first");
+ CollectionAssert.AreEqual(new[] { "second" }, list);
+ }
+
+ [Test]
+ public void IList_IsFixedSize()
+ {
+ var field = new RepeatedField<string> { "first", "second" };
+ IList list = field;
+ Assert.IsFalse(list.IsFixedSize);
+ }
+
+ [Test]
+ public void IList_IndexOf()
+ {
+ IList list = new RepeatedField<string> { "first", "second" };
+ Assert.AreEqual(1, list.IndexOf("second"));
+ Assert.AreEqual(-1, list.IndexOf("third"));
+ Assert.AreEqual(-1, list.IndexOf(new object()));
+ }
+
+ [Test]
+ public void IList_SyncRoot()
+ {
+ IList list = new RepeatedField<string> { "first", "second" };
+ Assert.AreSame(list, list.SyncRoot);
+ }
+
+ [Test]
+ public void IList_CopyTo()
+ {
+ IList list = new RepeatedField<string> { "first", "second" };
+ string[] stringArray = new string[4];
+ list.CopyTo(stringArray, 1);
+ CollectionAssert.AreEqual(new[] { null, "first", "second", null }, stringArray);
+
+ object[] objectArray = new object[4];
+ list.CopyTo(objectArray, 1);
+ CollectionAssert.AreEqual(new[] { null, "first", "second", null }, objectArray);
+
+ Assert.Throws<ArrayTypeMismatchException>(() => list.CopyTo(new StringBuilder[4], 1));
+ Assert.Throws<ArrayTypeMismatchException>(() => list.CopyTo(new int[4], 1));
+ }
+
+ [Test]
+ public void IList_IsSynchronized()
+ {
+ IList list = new RepeatedField<string> { "first", "second" };
+ Assert.IsFalse(list.IsSynchronized);
+ }
+
+ [Test]
+ public void IList_Insert()
+ {
+ IList list = new RepeatedField<string> { "first", "second" };
+ list.Insert(1, "middle");
+ CollectionAssert.AreEqual(new[] { "first", "middle", "second" }, list);
+ }
+
+ [Test]
+ public void ToString_Integers()
+ {
+ var list = new RepeatedField<int> { 5, 10, 20 };
+ var text = list.ToString();
+ Assert.AreEqual("[ 5, 10, 20 ]", text);
+ }
+
+ [Test]
+ public void ToString_Strings()
+ {
+ var list = new RepeatedField<string> { "x", "y", "z" };
+ var text = list.ToString();
+ Assert.AreEqual("[ \"x\", \"y\", \"z\" ]", text);
+ }
+
+ [Test]
+ public void ToString_Messages()
+ {
+ var list = new RepeatedField<TestAllTypes> { new TestAllTypes { SingleDouble = 1.5 }, new TestAllTypes { SingleInt32 = 10 } };
+ var text = list.ToString();
+ Assert.AreEqual("[ { \"singleDouble\": 1.5 }, { \"singleInt32\": 10 } ]", text);
+ }
+
+ [Test]
+ public void ToString_Empty()
+ {
+ var list = new RepeatedField<TestAllTypes> { };
+ var text = list.ToString();
+ Assert.AreEqual("[ ]", text);
+ }
+
+ [Test]
+ public void ToString_InvalidElementType()
+ {
+ var list = new RepeatedField<decimal> { 15m };
+ Assert.Throws<ArgumentException>(() => list.ToString());
+ }
+
+ [Test]
+ public void ToString_Timestamp()
+ {
+ var list = new RepeatedField<Timestamp> { Timestamp.FromDateTime(new DateTime(2015, 10, 1, 12, 34, 56, DateTimeKind.Utc)) };
+ var text = list.ToString();
+ Assert.AreEqual("[ \"2015-10-01T12:34:56Z\" ]", text);
+ }
+
+ [Test]
+ public void ToString_Struct()
+ {
+ var message = new Struct { Fields = { { "foo", new Value { NumberValue = 20 } } } };
+ var list = new RepeatedField<Struct> { message };
+ var text = list.ToString();
+ Assert.AreEqual(text, "[ { \"foo\": 20 } ]", message.ToString());
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Compatibility/PropertyInfoExtensionsTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Compatibility/PropertyInfoExtensionsTest.cs
new file mode 100644
index 00000000..df23a09c
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Compatibility/PropertyInfoExtensionsTest.cs
@@ -0,0 +1,98 @@
+#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.Reflection;
+
+namespace Google.Protobuf.Compatibility
+{
+ public class PropertyInfoExtensionsTest
+ {
+ public string PublicReadWrite { get; set; }
+ private string PrivateReadWrite { get; set; }
+ public string PublicReadPrivateWrite { get; private set; }
+ public string PrivateReadPublicWrite { private get; set; }
+ public string PublicReadOnly { get { return null; } }
+ private string PrivateReadOnly { get { return null; } }
+ public string PublicWriteOnly { set { } }
+ private string PrivateWriteOnly { set { } }
+
+ [Test]
+ [TestCase("PublicReadWrite")]
+ [TestCase("PublicReadPrivateWrite")]
+ [TestCase("PublicReadOnly")]
+ public void GetGetMethod_Success(string name)
+ {
+ var propertyInfo = typeof(PropertyInfoExtensionsTest)
+ .GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+ Assert.IsNotNull(PropertyInfoExtensions.GetGetMethod(propertyInfo));
+ }
+
+ [Test]
+ [TestCase("PrivateReadWrite")]
+ [TestCase("PrivateReadPublicWrite")]
+ [TestCase("PrivateReadOnly")]
+ [TestCase("PublicWriteOnly")]
+ [TestCase("PrivateWriteOnly")]
+ public void GetGetMethod_NoAccessibleGetter(string name)
+ {
+ var propertyInfo = typeof(PropertyInfoExtensionsTest)
+ .GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+ Assert.IsNull(PropertyInfoExtensions.GetGetMethod(propertyInfo));
+ }
+
+ [Test]
+ [TestCase("PublicReadWrite")]
+ [TestCase("PrivateReadPublicWrite")]
+ [TestCase("PublicWriteOnly")]
+ public void GetSetMethod_Success(string name)
+ {
+ var propertyInfo = typeof(PropertyInfoExtensionsTest)
+ .GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+ Assert.IsNotNull(PropertyInfoExtensions.GetSetMethod(propertyInfo));
+ }
+
+ [Test]
+ [TestCase("PublicReadPrivateWrite")]
+ [TestCase("PrivateReadWrite")]
+ [TestCase("PrivateReadOnly")]
+ [TestCase("PublicReadOnly")]
+ [TestCase("PrivateWriteOnly")]
+ public void GetSetMethod_NoAccessibleGetter(string name)
+ {
+ var propertyInfo = typeof(PropertyInfoExtensionsTest)
+ .GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+ Assert.IsNull(PropertyInfoExtensions.GetSetMethod(propertyInfo));
+ }
+ }
+
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Compatibility/TypeExtensionsTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Compatibility/TypeExtensionsTest.cs
new file mode 100644
index 00000000..f430b06b
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Compatibility/TypeExtensionsTest.cs
@@ -0,0 +1,117 @@
+#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 System.Collections.Generic;
+using System.Reflection;
+
+#if !DOTNET35
+namespace Google.Protobuf.Compatibility
+{
+ public class TypeExtensionsTest
+ {
+ public class DerivedList : List<string> { }
+ public string PublicProperty { get; set; }
+ private string PrivateProperty { get; set; }
+
+ public void PublicMethod()
+ {
+ }
+
+ private void PrivateMethod()
+ {
+ }
+
+ [Test]
+ [TestCase(typeof(object), typeof(string), true)]
+ [TestCase(typeof(object), typeof(int), true)]
+ [TestCase(typeof(string), typeof(string), true)]
+ [TestCase(typeof(string), typeof(object), false)]
+ [TestCase(typeof(string), typeof(int), false)]
+ [TestCase(typeof(int), typeof(int), true)]
+ [TestCase(typeof(ValueType), typeof(int), true)]
+ [TestCase(typeof(long), typeof(int), false)] //
+ public void IsAssignableFrom(Type target, Type argument, bool expected)
+ {
+ Assert.AreEqual(expected, TypeExtensions.IsAssignableFrom(target, argument));
+ }
+
+ [Test]
+ [TestCase(typeof(DerivedList), "Count")] // Go up the type hierarchy
+ [TestCase(typeof(List<string>), "Count")]
+ [TestCase(typeof(List<>), "Count")]
+ [TestCase(typeof(TypeExtensionsTest), "PublicProperty")]
+ public void GetProperty_Success(Type type, string name)
+ {
+ var property = TypeExtensions.GetProperty(type, name);
+ Assert.IsNotNull(property);
+ Assert.AreEqual(name, property.Name);
+ }
+
+ [Test]
+ [TestCase(typeof(TypeExtensionsTest), "PrivateProperty")]
+ [TestCase(typeof(TypeExtensionsTest), "Garbage")]
+ public void GetProperty_NoSuchProperty(Type type, string name)
+ {
+ var property = TypeExtensions.GetProperty(type, name);
+ Assert.IsNull(property);
+ }
+
+ [Test]
+ [TestCase(typeof(DerivedList), "RemoveAt")] // Go up the type hierarchy
+ [TestCase(typeof(List<>), "RemoveAt")]
+ [TestCase(typeof(TypeExtensionsTest), "PublicMethod")]
+ public void GetMethod_Success(Type type, string name)
+ {
+ var method = TypeExtensions.GetMethod(type, name);
+ Assert.IsNotNull(method);
+ Assert.AreEqual(name, method.Name);
+ }
+
+ [Test]
+ [TestCase(typeof(TypeExtensionsTest), "PrivateMethod")]
+ [TestCase(typeof(TypeExtensionsTest), "GarbageMethod")]
+ public void GetMethod_NoSuchMethod(Type type, string name)
+ {
+ var method = TypeExtensions.GetMethod(type, name);
+ Assert.IsNull(method);
+ }
+
+ [Test]
+ [TestCase(typeof(List<string>), "IndexOf")]
+ public void GetMethod_Ambiguous(Type type, string name)
+ {
+ Assert.Throws<AmbiguousMatchException>(() => TypeExtensions.GetMethod(type, name));
+ }
+ }
+}
+#endif
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/DeprecatedMemberTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/DeprecatedMemberTest.cs
new file mode 100644
index 00000000..34d5b9f9
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/DeprecatedMemberTest.cs
@@ -0,0 +1,55 @@
+#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 System;
+using System.Reflection;
+using Google.Protobuf.TestProtos;
+using NUnit.Framework;
+
+namespace Google.Protobuf
+{
+ public class DeprecatedMemberTest
+ {
+ private static void AssertIsDeprecated(MemberInfo member)
+ {
+ Assert.NotNull(member);
+ Assert.IsTrue(member.IsDefined(typeof(ObsoleteAttribute), false), "Member not obsolete: " + member);
+ }
+
+ [Test]
+ public void TestDepreatedPrimitiveValue()
+ {
+ AssertIsDeprecated(typeof(TestDeprecatedFields).GetProperty("DeprecatedInt32"));
+ }
+
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/EqualityTester.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/EqualityTester.cs
new file mode 100644
index 00000000..a669baba
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/EqualityTester.cs
@@ -0,0 +1,64 @@
+#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 System;
+using NUnit.Framework;
+
+namespace Google.Protobuf
+{
+ /// <summary>
+ /// Helper methods when testing equality. NUnit's Assert.AreEqual and
+ /// Assert.AreNotEqual methods try to be clever with collections, which can
+ /// be annoying...
+ /// </summary>
+ internal static class EqualityTester
+ {
+ public static void AssertEquality<T>(T first, T second) where T : IEquatable<T>
+ {
+ Assert.IsTrue(first.Equals(second));
+ Assert.IsTrue(first.Equals((object) second));
+ Assert.AreEqual(first.GetHashCode(), second.GetHashCode());
+ }
+
+ public static void AssertInequality<T>(T first, T second) where T : IEquatable<T>
+ {
+ Assert.IsFalse(first.Equals(second));
+ Assert.IsFalse(first.Equals((object) second));
+ // While this isn't a requirement, the chances of this test failing due to
+ // coincidence rather than a bug are very small.
+ if (first != null && second != null)
+ {
+ Assert.AreNotEqual(first.GetHashCode(), second.GetHashCode());
+ }
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/FieldCodecTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/FieldCodecTest.cs
new file mode 100644
index 00000000..0e2bad59
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/FieldCodecTest.cs
@@ -0,0 +1,196 @@
+#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 System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using Google.Protobuf.TestProtos;
+using NUnit.Framework;
+
+namespace Google.Protobuf
+{
+ public class FieldCodecTest
+ {
+#pragma warning disable 0414 // Used by tests via reflection - do not remove!
+ private static readonly List<ICodecTestData> Codecs = new List<ICodecTestData>
+ {
+ new FieldCodecTestData<bool>(FieldCodec.ForBool(100), true, "Bool"),
+ new FieldCodecTestData<string>(FieldCodec.ForString(100), "sample", "String"),
+ new FieldCodecTestData<ByteString>(FieldCodec.ForBytes(100), ByteString.CopyFrom(1, 2, 3), "Bytes"),
+ new FieldCodecTestData<int>(FieldCodec.ForInt32(100), -1000, "Int32"),
+ new FieldCodecTestData<int>(FieldCodec.ForSInt32(100), -1000, "SInt32"),
+ new FieldCodecTestData<int>(FieldCodec.ForSFixed32(100), -1000, "SFixed32"),
+ new FieldCodecTestData<uint>(FieldCodec.ForUInt32(100), 1234, "UInt32"),
+ new FieldCodecTestData<uint>(FieldCodec.ForFixed32(100), 1234, "Fixed32"),
+ new FieldCodecTestData<long>(FieldCodec.ForInt64(100), -1000, "Int64"),
+ new FieldCodecTestData<long>(FieldCodec.ForSInt64(100), -1000, "SInt64"),
+ new FieldCodecTestData<long>(FieldCodec.ForSFixed64(100), -1000, "SFixed64"),
+ new FieldCodecTestData<ulong>(FieldCodec.ForUInt64(100), 1234, "UInt64"),
+ new FieldCodecTestData<ulong>(FieldCodec.ForFixed64(100), 1234, "Fixed64"),
+ new FieldCodecTestData<float>(FieldCodec.ForFloat(100), 1234.5f, "Float"),
+ new FieldCodecTestData<double>(FieldCodec.ForDouble(100), 1234567890.5d, "Double"),
+ new FieldCodecTestData<ForeignEnum>(
+ FieldCodec.ForEnum(100, t => (int) t, t => (ForeignEnum) t), ForeignEnum.ForeignBaz, "Enum"),
+ new FieldCodecTestData<ForeignMessage>(
+ FieldCodec.ForMessage(100, ForeignMessage.Parser), new ForeignMessage { C = 10 }, "Message"),
+ };
+#pragma warning restore 0414
+
+ [Test, TestCaseSource("Codecs")]
+ public void RoundTripWithTag(ICodecTestData codec)
+ {
+ codec.TestRoundTripWithTag();
+ }
+
+ [Test, TestCaseSource("Codecs")]
+ public void RoundTripRaw(ICodecTestData codec)
+ {
+ codec.TestRoundTripRaw();
+ }
+
+ [Test, TestCaseSource("Codecs")]
+ public void CalculateSize(ICodecTestData codec)
+ {
+ codec.TestCalculateSizeWithTag();
+ }
+
+ [Test, TestCaseSource("Codecs")]
+ public void DefaultValue(ICodecTestData codec)
+ {
+ codec.TestDefaultValue();
+ }
+
+ [Test, TestCaseSource("Codecs")]
+ public void FixedSize(ICodecTestData codec)
+ {
+ codec.TestFixedSize();
+ }
+
+ // This is ugly, but it means we can have a non-generic interface.
+ // It feels like NUnit should support this better, but I don't know
+ // of any better ways right now.
+ public interface ICodecTestData
+ {
+ void TestRoundTripRaw();
+ void TestRoundTripWithTag();
+ void TestCalculateSizeWithTag();
+ void TestDefaultValue();
+ void TestFixedSize();
+ }
+
+ public class FieldCodecTestData<T> : ICodecTestData
+ {
+ private readonly FieldCodec<T> codec;
+ private readonly T sampleValue;
+ private readonly string name;
+
+ public FieldCodecTestData(FieldCodec<T> codec, T sampleValue, string name)
+ {
+ this.codec = codec;
+ this.sampleValue = sampleValue;
+ this.name = name;
+ }
+
+ public void TestRoundTripRaw()
+ {
+ var stream = new MemoryStream();
+ var codedOutput = new CodedOutputStream(stream);
+ codec.ValueWriter(codedOutput, sampleValue);
+ codedOutput.Flush();
+ stream.Position = 0;
+ var codedInput = new CodedInputStream(stream);
+ Assert.AreEqual(sampleValue, codec.ValueReader(codedInput));
+ Assert.IsTrue(codedInput.IsAtEnd);
+ }
+
+ public void TestRoundTripWithTag()
+ {
+ var stream = new MemoryStream();
+ var codedOutput = new CodedOutputStream(stream);
+ codec.WriteTagAndValue(codedOutput, sampleValue);
+ codedOutput.Flush();
+ stream.Position = 0;
+ var codedInput = new CodedInputStream(stream);
+ codedInput.AssertNextTag(codec.Tag);
+ Assert.AreEqual(sampleValue, codec.Read(codedInput));
+ Assert.IsTrue(codedInput.IsAtEnd);
+ }
+
+ public void TestCalculateSizeWithTag()
+ {
+ var stream = new MemoryStream();
+ var codedOutput = new CodedOutputStream(stream);
+ codec.WriteTagAndValue(codedOutput, sampleValue);
+ codedOutput.Flush();
+ Assert.AreEqual(stream.Position, codec.CalculateSizeWithTag(sampleValue));
+ }
+
+ public void TestDefaultValue()
+ {
+ // WriteTagAndValue ignores default values
+ var stream = new MemoryStream();
+ var codedOutput = new CodedOutputStream(stream);
+ codec.WriteTagAndValue(codedOutput, codec.DefaultValue);
+ codedOutput.Flush();
+ Assert.AreEqual(0, stream.Position);
+ Assert.AreEqual(0, codec.CalculateSizeWithTag(codec.DefaultValue));
+ if (typeof(T).GetTypeInfo().IsValueType)
+ {
+ Assert.AreEqual(default(T), codec.DefaultValue);
+ }
+
+ // The plain ValueWriter/ValueReader delegates don't.
+ if (codec.DefaultValue != null) // This part isn't appropriate for message types.
+ {
+ codedOutput = new CodedOutputStream(stream);
+ codec.ValueWriter(codedOutput, codec.DefaultValue);
+ codedOutput.Flush();
+ Assert.AreNotEqual(0, stream.Position);
+ Assert.AreEqual(stream.Position, codec.ValueSizeCalculator(codec.DefaultValue));
+ stream.Position = 0;
+ var codedInput = new CodedInputStream(stream);
+ Assert.AreEqual(codec.DefaultValue, codec.ValueReader(codedInput));
+ }
+ }
+
+ public void TestFixedSize()
+ {
+ Assert.AreEqual(name.Contains("Fixed"), codec.FixedSize != 0);
+ }
+
+ public override string ToString()
+ {
+ return name;
+ }
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/GeneratedMessageTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/GeneratedMessageTest.cs
new file mode 100644
index 00000000..8b153d69
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/GeneratedMessageTest.cs
@@ -0,0 +1,723 @@
+#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 System;
+using System.IO;
+using Google.Protobuf.TestProtos;
+using NUnit.Framework;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Google.Protobuf.WellKnownTypes;
+
+namespace Google.Protobuf
+{
+ /// <summary>
+ /// Tests around the generated TestAllTypes message.
+ /// </summary>
+ public class GeneratedMessageTest
+ {
+ [Test]
+ public void EmptyMessageFieldDistinctFromMissingMessageField()
+ {
+ // This demonstrates what we're really interested in...
+ var message1 = new TestAllTypes { SingleForeignMessage = new ForeignMessage() };
+ var message2 = new TestAllTypes(); // SingleForeignMessage is null
+ EqualityTester.AssertInequality(message1, message2);
+ }
+
+ [Test]
+ public void DefaultValues()
+ {
+ // Single fields
+ var message = new TestAllTypes();
+ Assert.AreEqual(false, message.SingleBool);
+ Assert.AreEqual(ByteString.Empty, message.SingleBytes);
+ Assert.AreEqual(0.0, message.SingleDouble);
+ Assert.AreEqual(0, message.SingleFixed32);
+ Assert.AreEqual(0L, message.SingleFixed64);
+ Assert.AreEqual(0.0f, message.SingleFloat);
+ Assert.AreEqual(ForeignEnum.ForeignUnspecified, message.SingleForeignEnum);
+ Assert.IsNull(message.SingleForeignMessage);
+ Assert.AreEqual(ImportEnum.Unspecified, message.SingleImportEnum);
+ Assert.IsNull(message.SingleImportMessage);
+ Assert.AreEqual(0, message.SingleInt32);
+ Assert.AreEqual(0L, message.SingleInt64);
+ Assert.AreEqual(TestAllTypes.Types.NestedEnum.Unspecified, message.SingleNestedEnum);
+ Assert.IsNull(message.SingleNestedMessage);
+ Assert.IsNull(message.SinglePublicImportMessage);
+ Assert.AreEqual(0, message.SingleSfixed32);
+ Assert.AreEqual(0L, message.SingleSfixed64);
+ Assert.AreEqual(0, message.SingleSint32);
+ Assert.AreEqual(0L, message.SingleSint64);
+ Assert.AreEqual("", message.SingleString);
+ Assert.AreEqual(0U, message.SingleUint32);
+ Assert.AreEqual(0UL, message.SingleUint64);
+
+ // Repeated fields
+ Assert.AreEqual(0, message.RepeatedBool.Count);
+ Assert.AreEqual(0, message.RepeatedBytes.Count);
+ Assert.AreEqual(0, message.RepeatedDouble.Count);
+ Assert.AreEqual(0, message.RepeatedFixed32.Count);
+ Assert.AreEqual(0, message.RepeatedFixed64.Count);
+ Assert.AreEqual(0, message.RepeatedFloat.Count);
+ Assert.AreEqual(0, message.RepeatedForeignEnum.Count);
+ Assert.AreEqual(0, message.RepeatedForeignMessage.Count);
+ Assert.AreEqual(0, message.RepeatedImportEnum.Count);
+ Assert.AreEqual(0, message.RepeatedImportMessage.Count);
+ Assert.AreEqual(0, message.RepeatedNestedEnum.Count);
+ Assert.AreEqual(0, message.RepeatedNestedMessage.Count);
+ Assert.AreEqual(0, message.RepeatedPublicImportMessage.Count);
+ Assert.AreEqual(0, message.RepeatedSfixed32.Count);
+ Assert.AreEqual(0, message.RepeatedSfixed64.Count);
+ Assert.AreEqual(0, message.RepeatedSint32.Count);
+ Assert.AreEqual(0, message.RepeatedSint64.Count);
+ Assert.AreEqual(0, message.RepeatedString.Count);
+ Assert.AreEqual(0, message.RepeatedUint32.Count);
+ Assert.AreEqual(0, message.RepeatedUint64.Count);
+
+ // Oneof fields
+ Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.None, message.OneofFieldCase);
+ Assert.AreEqual(0, message.OneofUint32);
+ Assert.AreEqual("", message.OneofString);
+ Assert.AreEqual(ByteString.Empty, message.OneofBytes);
+ Assert.IsNull(message.OneofNestedMessage);
+ }
+
+ [Test]
+ public void NullStringAndBytesRejected()
+ {
+ var message = new TestAllTypes();
+ Assert.Throws<ArgumentNullException>(() => message.SingleString = null);
+ Assert.Throws<ArgumentNullException>(() => message.OneofString = null);
+ Assert.Throws<ArgumentNullException>(() => message.SingleBytes = null);
+ Assert.Throws<ArgumentNullException>(() => message.OneofBytes = null);
+ }
+
+ [Test]
+ public void RoundTrip_Empty()
+ {
+ var message = new TestAllTypes();
+ // 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);
+ }
+
+ [Test]
+ public void RoundTrip_SingleValues()
+ {
+ var message = new TestAllTypes
+ {
+ SingleBool = true,
+ SingleBytes = ByteString.CopyFrom(1, 2, 3, 4),
+ SingleDouble = 23.5,
+ SingleFixed32 = 23,
+ SingleFixed64 = 1234567890123,
+ SingleFloat = 12.25f,
+ SingleForeignEnum = ForeignEnum.ForeignBar,
+ SingleForeignMessage = new ForeignMessage { C = 10 },
+ SingleImportEnum = ImportEnum.ImportBaz,
+ SingleImportMessage = new ImportMessage { D = 20 },
+ SingleInt32 = 100,
+ SingleInt64 = 3210987654321,
+ SingleNestedEnum = TestAllTypes.Types.NestedEnum.Foo,
+ SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 35 },
+ SinglePublicImportMessage = new PublicImportMessage { E = 54 },
+ SingleSfixed32 = -123,
+ SingleSfixed64 = -12345678901234,
+ SingleSint32 = -456,
+ SingleSint64 = -12345678901235,
+ SingleString = "test",
+ SingleUint32 = uint.MaxValue,
+ SingleUint64 = ulong.MaxValue
+ };
+
+ byte[] bytes = message.ToByteArray();
+ TestAllTypes parsed = TestAllTypes.Parser.ParseFrom(bytes);
+ Assert.AreEqual(message, parsed);
+ }
+
+ [Test]
+ public void RoundTrip_RepeatedValues()
+ {
+ var message = new TestAllTypes
+ {
+ RepeatedBool = { true, false },
+ RepeatedBytes = { ByteString.CopyFrom(1, 2, 3, 4), ByteString.CopyFrom(5, 6) },
+ RepeatedDouble = { -12.25, 23.5 },
+ RepeatedFixed32 = { uint.MaxValue, 23 },
+ RepeatedFixed64 = { ulong.MaxValue, 1234567890123 },
+ RepeatedFloat = { 100f, 12.25f },
+ RepeatedForeignEnum = { ForeignEnum.ForeignFoo, ForeignEnum.ForeignBar },
+ RepeatedForeignMessage = { new ForeignMessage(), new ForeignMessage { C = 10 } },
+ RepeatedImportEnum = { ImportEnum.ImportBaz, ImportEnum.Unspecified },
+ RepeatedImportMessage = { new ImportMessage { D = 20 }, new ImportMessage { D = 25 } },
+ RepeatedInt32 = { 100, 200 },
+ RepeatedInt64 = { 3210987654321, long.MaxValue },
+ RepeatedNestedEnum = { TestAllTypes.Types.NestedEnum.Foo, TestAllTypes.Types.NestedEnum.Neg },
+ RepeatedNestedMessage = { new TestAllTypes.Types.NestedMessage { Bb = 35 }, new TestAllTypes.Types.NestedMessage { Bb = 10 } },
+ RepeatedPublicImportMessage = { new PublicImportMessage { E = 54 }, new PublicImportMessage { E = -1 } },
+ RepeatedSfixed32 = { -123, 123 },
+ RepeatedSfixed64 = { -12345678901234, 12345678901234 },
+ RepeatedSint32 = { -456, 100 },
+ RepeatedSint64 = { -12345678901235, 123 },
+ RepeatedString = { "foo", "bar" },
+ RepeatedUint32 = { uint.MaxValue, uint.MinValue },
+ RepeatedUint64 = { ulong.MaxValue, uint.MinValue }
+ };
+
+ byte[] bytes = message.ToByteArray();
+ TestAllTypes parsed = TestAllTypes.Parser.ParseFrom(bytes);
+ Assert.AreEqual(message, parsed);
+ }
+
+ // Note that not every map within map_unittest_proto3 is used. They all go through very
+ // similar code paths. The fact that all maps are present is validation that we have codecs
+ // for every type.
+ [Test]
+ public void RoundTrip_Maps()
+ {
+ var message = new TestMap
+ {
+ MapBoolBool = {
+ { false, true },
+ { true, false }
+ },
+ MapInt32Bytes = {
+ { 5, ByteString.CopyFrom(6, 7, 8) },
+ { 25, ByteString.CopyFrom(1, 2, 3, 4, 5) },
+ { 10, ByteString.Empty }
+ },
+ MapInt32ForeignMessage = {
+ { 0, new ForeignMessage { C = 10 } },
+ { 5, new ForeignMessage() },
+ },
+ MapInt32Enum = {
+ { 1, MapEnum.Bar },
+ { 2000, MapEnum.Foo }
+ }
+ };
+
+ byte[] bytes = message.ToByteArray();
+ TestMap parsed = TestMap.Parser.ParseFrom(bytes);
+ Assert.AreEqual(message, parsed);
+ }
+
+ [Test]
+ public void MapWithEmptyEntry()
+ {
+ var message = new TestMap
+ {
+ MapInt32Bytes = { { 0, ByteString.Empty } }
+ };
+
+ 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]);
+ }
+
+ [Test]
+ public void MapWithOnlyValue()
+ {
+ // Hand-craft the stream to contain a single entry with just a value.
+ var memoryStream = new MemoryStream();
+ var output = new CodedOutputStream(memoryStream);
+ output.WriteTag(TestMap.MapInt32ForeignMessageFieldNumber, WireFormat.WireType.LengthDelimited);
+ var nestedMessage = new ForeignMessage { C = 20 };
+ // Size of the entry (tag, size written by WriteMessage, data written by WriteMessage)
+ output.WriteLength(2 + nestedMessage.CalculateSize());
+ output.WriteTag(2, WireFormat.WireType.LengthDelimited);
+ output.WriteMessage(nestedMessage);
+ output.Flush();
+
+ var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
+ Assert.AreEqual(nestedMessage, parsed.MapInt32ForeignMessage[0]);
+ }
+
+ [Test]
+ public void MapWithOnlyKey_PrimitiveValue()
+ {
+ // Hand-craft the stream to contain a single entry with just a key.
+ var memoryStream = new MemoryStream();
+ var output = new CodedOutputStream(memoryStream);
+ output.WriteTag(TestMap.MapInt32DoubleFieldNumber, WireFormat.WireType.LengthDelimited);
+ int key = 10;
+ output.WriteLength(1 + CodedOutputStream.ComputeInt32Size(key));
+ output.WriteTag(1, WireFormat.WireType.Varint);
+ output.WriteInt32(key);
+ output.Flush();
+
+ var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
+ Assert.AreEqual(0.0, parsed.MapInt32Double[key]);
+ }
+
+ [Test]
+ public void MapWithOnlyKey_MessageValue()
+ {
+ // Hand-craft the stream to contain a single entry with just a key.
+ var memoryStream = new MemoryStream();
+ var output = new CodedOutputStream(memoryStream);
+ output.WriteTag(TestMap.MapInt32ForeignMessageFieldNumber, WireFormat.WireType.LengthDelimited);
+ int key = 10;
+ output.WriteLength(1 + CodedOutputStream.ComputeInt32Size(key));
+ output.WriteTag(1, WireFormat.WireType.Varint);
+ output.WriteInt32(key);
+ output.Flush();
+
+ var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
+ Assert.AreEqual(new ForeignMessage(), parsed.MapInt32ForeignMessage[key]);
+ }
+
+ [Test]
+ public void MapIgnoresExtraFieldsWithinEntryMessages()
+ {
+ // Hand-craft the stream to contain a single entry with three fields
+ var memoryStream = new MemoryStream();
+ var output = new CodedOutputStream(memoryStream);
+
+ output.WriteTag(TestMap.MapInt32Int32FieldNumber, WireFormat.WireType.LengthDelimited);
+
+ var key = 10; // Field 1
+ var value = 20; // Field 2
+ var extra = 30; // Field 3
+
+ // Each field can be represented in a single byte, with a single byte tag.
+ // Total message size: 6 bytes.
+ output.WriteLength(6);
+ output.WriteTag(1, WireFormat.WireType.Varint);
+ output.WriteInt32(key);
+ output.WriteTag(2, WireFormat.WireType.Varint);
+ output.WriteInt32(value);
+ output.WriteTag(3, WireFormat.WireType.Varint);
+ output.WriteInt32(extra);
+ output.Flush();
+
+ var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
+ Assert.AreEqual(value, parsed.MapInt32Int32[key]);
+ }
+
+ [Test]
+ public void MapFieldOrderIsIrrelevant()
+ {
+ var memoryStream = new MemoryStream();
+ var output = new CodedOutputStream(memoryStream);
+
+ output.WriteTag(TestMap.MapInt32Int32FieldNumber, WireFormat.WireType.LengthDelimited);
+
+ var key = 10;
+ var value = 20;
+
+ // Each field can be represented in a single byte, with a single byte tag.
+ // Total message size: 4 bytes.
+ output.WriteLength(4);
+ output.WriteTag(2, WireFormat.WireType.Varint);
+ output.WriteInt32(value);
+ output.WriteTag(1, WireFormat.WireType.Varint);
+ output.WriteInt32(key);
+ output.Flush();
+
+ var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
+ Assert.AreEqual(value, parsed.MapInt32Int32[key]);
+ }
+
+ [Test]
+ public void MapNonContiguousEntries()
+ {
+ var memoryStream = new MemoryStream();
+ var output = new CodedOutputStream(memoryStream);
+
+ // Message structure:
+ // Entry for MapInt32Int32
+ // Entry for MapStringString
+ // Entry for MapInt32Int32
+
+ // First entry
+ var key1 = 10;
+ var value1 = 20;
+ output.WriteTag(TestMap.MapInt32Int32FieldNumber, WireFormat.WireType.LengthDelimited);
+ output.WriteLength(4);
+ output.WriteTag(1, WireFormat.WireType.Varint);
+ output.WriteInt32(key1);
+ output.WriteTag(2, WireFormat.WireType.Varint);
+ output.WriteInt32(value1);
+
+ // Second entry
+ var key2 = "a";
+ var value2 = "b";
+ output.WriteTag(TestMap.MapStringStringFieldNumber, WireFormat.WireType.LengthDelimited);
+ output.WriteLength(6); // 3 bytes per entry: tag, size, character
+ output.WriteTag(1, WireFormat.WireType.LengthDelimited);
+ output.WriteString(key2);
+ output.WriteTag(2, WireFormat.WireType.LengthDelimited);
+ output.WriteString(value2);
+
+ // Third entry
+ var key3 = 15;
+ var value3 = 25;
+ output.WriteTag(TestMap.MapInt32Int32FieldNumber, WireFormat.WireType.LengthDelimited);
+ output.WriteLength(4);
+ output.WriteTag(1, WireFormat.WireType.Varint);
+ output.WriteInt32(key3);
+ output.WriteTag(2, WireFormat.WireType.Varint);
+ 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);
+ }
+
+ [Test]
+ public void DuplicateKeys_LastEntryWins()
+ {
+ var memoryStream = new MemoryStream();
+ var output = new CodedOutputStream(memoryStream);
+
+ var key = 10;
+ var value1 = 20;
+ var value2 = 30;
+
+ // First entry
+ output.WriteTag(TestMap.MapInt32Int32FieldNumber, WireFormat.WireType.LengthDelimited);
+ output.WriteLength(4);
+ output.WriteTag(1, WireFormat.WireType.Varint);
+ output.WriteInt32(key);
+ output.WriteTag(2, WireFormat.WireType.Varint);
+ output.WriteInt32(value1);
+
+ // Second entry - same key, different value
+ output.WriteTag(TestMap.MapInt32Int32FieldNumber, WireFormat.WireType.LengthDelimited);
+ output.WriteLength(4);
+ output.WriteTag(1, WireFormat.WireType.Varint);
+ output.WriteInt32(key);
+ output.WriteTag(2, WireFormat.WireType.Varint);
+ output.WriteInt32(value2);
+ output.Flush();
+
+ var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
+ Assert.AreEqual(value2, parsed.MapInt32Int32[key]);
+ }
+
+ [Test]
+ public void CloneSingleNonMessageValues()
+ {
+ var original = new TestAllTypes
+ {
+ SingleBool = true,
+ SingleBytes = ByteString.CopyFrom(1, 2, 3, 4),
+ SingleDouble = 23.5,
+ SingleFixed32 = 23,
+ SingleFixed64 = 1234567890123,
+ SingleFloat = 12.25f,
+ SingleInt32 = 100,
+ SingleInt64 = 3210987654321,
+ SingleNestedEnum = TestAllTypes.Types.NestedEnum.Foo,
+ SingleSfixed32 = -123,
+ SingleSfixed64 = -12345678901234,
+ SingleSint32 = -456,
+ SingleSint64 = -12345678901235,
+ SingleString = "test",
+ SingleUint32 = uint.MaxValue,
+ SingleUint64 = ulong.MaxValue
+ };
+ var clone = original.Clone();
+ Assert.AreNotSame(original, clone);
+ Assert.AreEqual(original, clone);
+ // Just as a single example
+ clone.SingleInt32 = 150;
+ Assert.AreNotEqual(original, clone);
+ }
+
+ [Test]
+ public void CloneRepeatedNonMessageValues()
+ {
+ var original = new TestAllTypes
+ {
+ RepeatedBool = { true, false },
+ RepeatedBytes = { ByteString.CopyFrom(1, 2, 3, 4), ByteString.CopyFrom(5, 6) },
+ RepeatedDouble = { -12.25, 23.5 },
+ RepeatedFixed32 = { uint.MaxValue, 23 },
+ RepeatedFixed64 = { ulong.MaxValue, 1234567890123 },
+ RepeatedFloat = { 100f, 12.25f },
+ RepeatedInt32 = { 100, 200 },
+ RepeatedInt64 = { 3210987654321, long.MaxValue },
+ RepeatedNestedEnum = { TestAllTypes.Types.NestedEnum.Foo, TestAllTypes.Types.NestedEnum.Neg },
+ RepeatedSfixed32 = { -123, 123 },
+ RepeatedSfixed64 = { -12345678901234, 12345678901234 },
+ RepeatedSint32 = { -456, 100 },
+ RepeatedSint64 = { -12345678901235, 123 },
+ RepeatedString = { "foo", "bar" },
+ RepeatedUint32 = { uint.MaxValue, uint.MinValue },
+ RepeatedUint64 = { ulong.MaxValue, uint.MinValue }
+ };
+
+ var clone = original.Clone();
+ Assert.AreNotSame(original, clone);
+ Assert.AreEqual(original, clone);
+ // Just as a single example
+ clone.RepeatedDouble.Add(25.5);
+ Assert.AreNotEqual(original, clone);
+ }
+
+ [Test]
+ public void CloneSingleMessageField()
+ {
+ var original = new TestAllTypes
+ {
+ SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 20 }
+ };
+
+ var clone = original.Clone();
+ Assert.AreNotSame(original, clone);
+ Assert.AreNotSame(original.SingleNestedMessage, clone.SingleNestedMessage);
+ Assert.AreEqual(original, clone);
+
+ clone.SingleNestedMessage.Bb = 30;
+ Assert.AreNotEqual(original, clone);
+ }
+
+ [Test]
+ public void CloneRepeatedMessageField()
+ {
+ var original = new TestAllTypes
+ {
+ RepeatedNestedMessage = { new TestAllTypes.Types.NestedMessage { Bb = 20 } }
+ };
+
+ var clone = original.Clone();
+ Assert.AreNotSame(original, clone);
+ Assert.AreNotSame(original.RepeatedNestedMessage, clone.RepeatedNestedMessage);
+ Assert.AreNotSame(original.RepeatedNestedMessage[0], clone.RepeatedNestedMessage[0]);
+ Assert.AreEqual(original, clone);
+
+ clone.RepeatedNestedMessage[0].Bb = 30;
+ Assert.AreNotEqual(original, clone);
+ }
+
+ [Test]
+ public void CloneOneofField()
+ {
+ var original = new TestAllTypes
+ {
+ OneofNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 20 }
+ };
+
+ var clone = original.Clone();
+ Assert.AreNotSame(original, clone);
+ Assert.AreEqual(original, clone);
+
+ // We should have cloned the message
+ original.OneofNestedMessage.Bb = 30;
+ Assert.AreNotEqual(original, clone);
+ }
+
+ [Test]
+ public void OneofProperties()
+ {
+ // Switch the oneof case between each of the different options, and check everything behaves
+ // as expected in each case.
+ var message = new TestAllTypes();
+ Assert.AreEqual("", message.OneofString);
+ Assert.AreEqual(0, message.OneofUint32);
+ Assert.AreEqual(ByteString.Empty, message.OneofBytes);
+ Assert.IsNull(message.OneofNestedMessage);
+ Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.None, message.OneofFieldCase);
+
+ message.OneofString = "sample";
+ Assert.AreEqual("sample", message.OneofString);
+ Assert.AreEqual(0, message.OneofUint32);
+ Assert.AreEqual(ByteString.Empty, message.OneofBytes);
+ Assert.IsNull(message.OneofNestedMessage);
+ Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofString, message.OneofFieldCase);
+
+ var bytes = ByteString.CopyFrom(1, 2, 3);
+ message.OneofBytes = bytes;
+ Assert.AreEqual("", message.OneofString);
+ Assert.AreEqual(0, message.OneofUint32);
+ Assert.AreEqual(bytes, message.OneofBytes);
+ Assert.IsNull(message.OneofNestedMessage);
+ Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofBytes, message.OneofFieldCase);
+
+ message.OneofUint32 = 20;
+ Assert.AreEqual("", message.OneofString);
+ Assert.AreEqual(20, message.OneofUint32);
+ Assert.AreEqual(ByteString.Empty, message.OneofBytes);
+ Assert.IsNull(message.OneofNestedMessage);
+ Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofUint32, message.OneofFieldCase);
+
+ var nestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 25 };
+ message.OneofNestedMessage = nestedMessage;
+ Assert.AreEqual("", message.OneofString);
+ Assert.AreEqual(0, message.OneofUint32);
+ Assert.AreEqual(ByteString.Empty, message.OneofBytes);
+ Assert.AreEqual(nestedMessage, message.OneofNestedMessage);
+ Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofNestedMessage, message.OneofFieldCase);
+
+ message.ClearOneofField();
+ Assert.AreEqual("", message.OneofString);
+ Assert.AreEqual(0, message.OneofUint32);
+ Assert.AreEqual(ByteString.Empty, message.OneofBytes);
+ Assert.IsNull(message.OneofNestedMessage);
+ Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.None, message.OneofFieldCase);
+ }
+
+ [Test]
+ public void Oneof_DefaultValuesNotEqual()
+ {
+ var message1 = new TestAllTypes { OneofString = "" };
+ var message2 = new TestAllTypes { OneofUint32 = 0 };
+ Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofString, message1.OneofFieldCase);
+ Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofUint32, message2.OneofFieldCase);
+ Assert.AreNotEqual(message1, message2);
+ }
+
+ [Test]
+ public void OneofSerialization_NonDefaultValue()
+ {
+ var message = new TestAllTypes();
+ message.OneofString = "this would take a bit of space";
+ message.OneofUint32 = 10;
+ 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);
+ }
+
+ [Test]
+ public void OneofSerialization_DefaultValue()
+ {
+ var message = new TestAllTypes();
+ message.OneofString = "this would take a bit of space";
+ message.OneofUint32 = 0; // This is the default value for UInt32; normally wouldn't be serialized
+ 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);
+ }
+
+ [Test]
+ public void IgnoreUnknownFields_RealDataStillRead()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ var unusedFieldNumber = 23456;
+ Assert.IsFalse(TestAllTypes.Descriptor.Fields.InDeclarationOrder().Select(x => x.FieldNumber).Contains(unusedFieldNumber));
+ output.WriteTag(unusedFieldNumber, WireFormat.WireType.LengthDelimited);
+ output.WriteString("ignore me");
+ message.WriteTo(output);
+ output.Flush();
+
+ stream.Position = 0;
+ var parsed = TestAllTypes.Parser.ParseFrom(stream);
+ Assert.AreEqual(message, parsed);
+ }
+
+ [Test]
+ public void IgnoreUnknownFields_AllTypes()
+ {
+ // Simple way of ensuring we can skip all kinds of fields.
+ var data = SampleMessages.CreateFullTestAllTypes().ToByteArray();
+ var empty = Empty.Parser.ParseFrom(data);
+ Assert.AreEqual(new Empty(), empty);
+ }
+
+ // This was originally seen as a conformance test failure.
+ [Test]
+ public void TruncatedMessageFieldThrows()
+ {
+ // 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<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseFrom(data));
+ }
+
+ /// <summary>
+ /// Demonstrates current behaviour with an extraneous end group tag - see issue 688
+ /// for details; we may want to change this.
+ /// </summary>
+ [Test]
+ public void ExtraEndGroupThrows()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+
+ output.WriteTag(TestAllTypes.SingleFixed32FieldNumber, WireFormat.WireType.Fixed32);
+ output.WriteFixed32(123);
+ output.WriteTag(100, WireFormat.WireType.EndGroup);
+
+ output.Flush();
+
+ stream.Position = 0;
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseFrom(stream));
+ }
+
+ [Test]
+ public void CustomDiagnosticMessage_DirectToStringCall()
+ {
+ var message = new ForeignMessage { C = 31 };
+ Assert.AreEqual("{ \"c\": 31, \"@cInHex\": \"1f\" }", message.ToString());
+ Assert.AreEqual("{ \"c\": 31 }", JsonFormatter.Default.Format(message));
+ }
+
+ [Test]
+ public void CustomDiagnosticMessage_Nested()
+ {
+ var message = new TestAllTypes { SingleForeignMessage = new ForeignMessage { C = 16 } };
+ Assert.AreEqual("{ \"singleForeignMessage\": { \"c\": 16, \"@cInHex\": \"10\" } }", message.ToString());
+ Assert.AreEqual("{ \"singleForeignMessage\": { \"c\": 16 } }", JsonFormatter.Default.Format(message));
+ }
+
+ [Test]
+ public void CustomDiagnosticMessage_DirectToTextWriterCall()
+ {
+ var message = new ForeignMessage { C = 31 };
+ var writer = new StringWriter();
+ JsonFormatter.Default.Format(message, writer);
+ Assert.AreEqual("{ \"c\": 31 }", writer.ToString());
+ }
+ }
+} \ No newline at end of file
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Google.Protobuf.Test.xproj b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Google.Protobuf.Test.xproj
new file mode 100644
index 00000000..a9a1cc04
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Google.Protobuf.Test.xproj
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
+ <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+ </PropertyGroup>
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>580eb013-d3c7-4578-b845-015f4a3b0591</ProjectGuid>
+ <RootNamespace>Google.Protobuf.Test</RootNamespace>
+ <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
+ <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <SchemaVersion>2.0</SchemaVersion>
+ </PropertyGroup>
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
+</Project> \ No newline at end of file
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/IssuesTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/IssuesTest.cs
new file mode 100644
index 00000000..a38d6b08
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/IssuesTest.cs
@@ -0,0 +1,82 @@
+#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.Reflection;
+using UnitTest.Issues.TestProtos;
+using NUnit.Framework;
+
+
+namespace Google.Protobuf
+{
+ /// <summary>
+ /// Tests for issues which aren't easily compartmentalized into other unit tests.
+ /// </summary>
+ public class IssuesTest
+ {
+ // Issue 45
+ [Test]
+ public void FieldCalledItem()
+ {
+ ItemField message = new ItemField { Item = 3 };
+ FieldDescriptor field = ItemField.Descriptor.FindFieldByName("item");
+ Assert.NotNull(field);
+ Assert.AreEqual(3, (int)field.Accessor.GetValue(message));
+ }
+
+ [Test]
+ public void ReservedNames()
+ {
+ var message = new ReservedNames { Types_ = 10, Descriptor_ = 20 };
+ // Underscores aren't reflected in the JSON.
+ Assert.AreEqual("{ \"types\": 10, \"descriptor\": 20 }", message.ToString());
+ }
+
+ [Test]
+ public void JsonNameParseTest()
+ {
+ var settings = new JsonParser.Settings(10, TypeRegistry.FromFiles(UnittestIssuesReflection.Descriptor));
+ var parser = new JsonParser(settings);
+
+ // It is safe to use either original field name or explicitly specified json_name
+ Assert.AreEqual(new TestJsonName { Name = "test", Description = "test2", Guid = "test3" },
+ parser.Parse<TestJsonName>("{ \"name\": \"test\", \"desc\": \"test2\", \"guid\": \"test3\" }"));
+ }
+
+ [Test]
+ public void JsonNameFormatTest()
+ {
+ var message = new TestJsonName { Name = "test", Description = "test2", Guid = "test3" };
+ Assert.AreEqual("{ \"name\": \"test\", \"desc\": \"test2\", \"exid\": \"test3\" }",
+ JsonFormatter.Default.Format(message));
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/JsonParserTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/JsonParserTest.cs
new file mode 100644
index 00000000..f595455a
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/JsonParserTest.cs
@@ -0,0 +1,939 @@
+#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.Reflection;
+using Google.Protobuf.TestProtos;
+using Google.Protobuf.WellKnownTypes;
+using NUnit.Framework;
+using System;
+
+namespace Google.Protobuf
+{
+ /// <summary>
+ /// Unit tests for JSON parsing.
+ /// </summary>
+ public class JsonParserTest
+ {
+ // Sanity smoke test
+ [Test]
+ public void AllTypesRoundtrip()
+ {
+ AssertRoundtrip(SampleMessages.CreateFullTestAllTypes());
+ }
+
+ [Test]
+ public void Maps()
+ {
+ AssertRoundtrip(new TestMap { MapStringString = { { "with spaces", "bar" }, { "a", "b" } } });
+ AssertRoundtrip(new TestMap { MapInt32Int32 = { { 0, 1 }, { 2, 3 } } });
+ AssertRoundtrip(new TestMap { MapBoolBool = { { false, true }, { true, false } } });
+ }
+
+ [Test]
+ [TestCase(" 1 ")]
+ [TestCase("+1")]
+ [TestCase("1,000")]
+ [TestCase("1.5")]
+ public void IntegerMapKeysAreStrict(string keyText)
+ {
+ // Test that integer parsing is strict. We assume that if this is correct for int32,
+ // it's correct for other numeric key types.
+ var json = "{ \"mapInt32Int32\": { \"" + keyText + "\" : \"1\" } }";
+ Assert.Throws<InvalidProtocolBufferException>(() => JsonParser.Default.Parse<TestMap>(json));
+ }
+
+ [Test]
+ public void OriginalFieldNameAccepted()
+ {
+ var json = "{ \"single_int32\": 10 }";
+ var expected = new TestAllTypes { SingleInt32 = 10 };
+ Assert.AreEqual(expected, TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ public void SourceContextRoundtrip()
+ {
+ AssertRoundtrip(new SourceContext { FileName = "foo.proto" });
+ }
+
+ [Test]
+ public void SingularWrappers_DefaultNonNullValues()
+ {
+ var message = new TestWellKnownTypes
+ {
+ StringField = "",
+ BytesField = ByteString.Empty,
+ BoolField = false,
+ FloatField = 0f,
+ DoubleField = 0d,
+ Int32Field = 0,
+ Int64Field = 0,
+ Uint32Field = 0,
+ Uint64Field = 0
+ };
+ AssertRoundtrip(message);
+ }
+
+ [Test]
+ public void SingularWrappers_NonDefaultValues()
+ {
+ var message = new TestWellKnownTypes
+ {
+ StringField = "x",
+ BytesField = ByteString.CopyFrom(1, 2, 3),
+ BoolField = true,
+ FloatField = 12.5f,
+ DoubleField = 12.25d,
+ Int32Field = 1,
+ Int64Field = 2,
+ Uint32Field = 3,
+ Uint64Field = 4
+ };
+ AssertRoundtrip(message);
+ }
+
+ [Test]
+ public void SingularWrappers_ExplicitNulls()
+ {
+ // When we parse the "valueField": null part, we remember it... basically, it's one case
+ // where explicit default values don't fully roundtrip.
+ var message = new TestWellKnownTypes { ValueField = Value.ForNull() };
+ var json = new JsonFormatter(new JsonFormatter.Settings(true)).Format(message);
+ var parsed = JsonParser.Default.Parse<TestWellKnownTypes>(json);
+ Assert.AreEqual(message, parsed);
+ }
+
+ [Test]
+ [TestCase(typeof(BoolValue), "true", true)]
+ [TestCase(typeof(Int32Value), "32", 32)]
+ [TestCase(typeof(Int64Value), "32", 32L)]
+ [TestCase(typeof(Int64Value), "\"32\"", 32L)]
+ [TestCase(typeof(UInt32Value), "32", 32U)]
+ [TestCase(typeof(UInt64Value), "\"32\"", 32UL)]
+ [TestCase(typeof(UInt64Value), "32", 32UL)]
+ [TestCase(typeof(StringValue), "\"foo\"", "foo")]
+ [TestCase(typeof(FloatValue), "1.5", 1.5f)]
+ [TestCase(typeof(DoubleValue), "1.5", 1.5d)]
+ public void Wrappers_Standalone(System.Type wrapperType, string json, object expectedValue)
+ {
+ IMessage parsed = (IMessage)Activator.CreateInstance(wrapperType);
+ IMessage expected = (IMessage)Activator.CreateInstance(wrapperType);
+ JsonParser.Default.Merge(parsed, "null");
+ Assert.AreEqual(expected, parsed);
+
+ JsonParser.Default.Merge(parsed, json);
+ expected.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber].Accessor.SetValue(expected, expectedValue);
+ Assert.AreEqual(expected, parsed);
+ }
+
+ [Test]
+ public void ExplicitNullValue()
+ {
+ string json = "{\"valueField\": null}";
+ var message = JsonParser.Default.Parse<TestWellKnownTypes>(json);
+ Assert.AreEqual(new TestWellKnownTypes { ValueField = Value.ForNull() }, message);
+ }
+
+ [Test]
+ public void BytesWrapper_Standalone()
+ {
+ ByteString data = ByteString.CopyFrom(1, 2, 3);
+ // Can't do this with attributes...
+ var parsed = JsonParser.Default.Parse<BytesValue>(WrapInQuotes(data.ToBase64()));
+ var expected = new BytesValue { Value = data };
+ Assert.AreEqual(expected, parsed);
+ }
+
+ [Test]
+ public void RepeatedWrappers()
+ {
+ var message = new RepeatedWellKnownTypes
+ {
+ BoolField = { true, false },
+ BytesField = { ByteString.CopyFrom(1, 2, 3), ByteString.CopyFrom(4, 5, 6), ByteString.Empty },
+ DoubleField = { 12.5, -1.5, 0d },
+ FloatField = { 123.25f, -20f, 0f },
+ Int32Field = { int.MaxValue, int.MinValue, 0 },
+ Int64Field = { long.MaxValue, long.MinValue, 0L },
+ StringField = { "First", "Second", "" },
+ Uint32Field = { uint.MaxValue, uint.MinValue, 0U },
+ Uint64Field = { ulong.MaxValue, ulong.MinValue, 0UL },
+ };
+ AssertRoundtrip(message);
+ }
+
+ [Test]
+ public void RepeatedField_NullElementProhibited()
+ {
+ string json = "{ \"repeated_foreign_message\": [null] }";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ public void RepeatedField_NullOverallValueAllowed()
+ {
+ string json = "{ \"repeated_foreign_message\": null }";
+ Assert.AreEqual(new TestAllTypes(), TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("{ \"mapInt32Int32\": { \"10\": null }")]
+ [TestCase("{ \"mapStringString\": { \"abc\": null }")]
+ [TestCase("{ \"mapInt32ForeignMessage\": { \"10\": null }")]
+ public void MapField_NullValueProhibited(string json)
+ {
+ Assert.Throws<InvalidProtocolBufferException>(() => TestMap.Parser.ParseJson(json));
+ }
+
+ [Test]
+ public void MapField_NullOverallValueAllowed()
+ {
+ string json = "{ \"mapInt32Int32\": null }";
+ Assert.AreEqual(new TestMap(), TestMap.Parser.ParseJson(json));
+ }
+
+ [Test]
+ public void IndividualWrapperTypes()
+ {
+ Assert.AreEqual(new StringValue { Value = "foo" }, StringValue.Parser.ParseJson("\"foo\""));
+ Assert.AreEqual(new Int32Value { Value = 1 }, Int32Value.Parser.ParseJson("1"));
+ // Can parse strings directly too
+ Assert.AreEqual(new Int32Value { Value = 1 }, Int32Value.Parser.ParseJson("\"1\""));
+ }
+
+ private static void AssertRoundtrip<T>(T message) where T : IMessage<T>, new()
+ {
+ var clone = message.Clone();
+ var json = JsonFormatter.Default.Format(message);
+ var parsed = JsonParser.Default.Parse<T>(json);
+ Assert.AreEqual(clone, parsed);
+ }
+
+ [Test]
+ [TestCase("0", 0)]
+ [TestCase("-0", 0)] // Not entirely clear whether we intend to allow this...
+ [TestCase("1", 1)]
+ [TestCase("-1", -1)]
+ [TestCase("2147483647", 2147483647)]
+ [TestCase("-2147483648", -2147483648)]
+ public void StringToInt32_Valid(string jsonValue, int expectedParsedValue)
+ {
+ string json = "{ \"singleInt32\": \"" + jsonValue + "\"}";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(expectedParsedValue, parsed.SingleInt32);
+ }
+
+ [Test]
+ [TestCase("+0")]
+ [TestCase(" 1")]
+ [TestCase("1 ")]
+ [TestCase("00")]
+ [TestCase("-00")]
+ [TestCase("--1")]
+ [TestCase("+1")]
+ [TestCase("1.5")]
+ [TestCase("1e10")]
+ [TestCase("2147483648")]
+ [TestCase("-2147483649")]
+ public void StringToInt32_Invalid(string jsonValue)
+ {
+ string json = "{ \"singleInt32\": \"" + jsonValue + "\"}";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("0", 0U)]
+ [TestCase("1", 1U)]
+ [TestCase("4294967295", 4294967295U)]
+ public void StringToUInt32_Valid(string jsonValue, uint expectedParsedValue)
+ {
+ string json = "{ \"singleUint32\": \"" + jsonValue + "\"}";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(expectedParsedValue, parsed.SingleUint32);
+ }
+
+ // Assume that anything non-bounds-related is covered in the Int32 case
+ [Test]
+ [TestCase("-1")]
+ [TestCase("4294967296")]
+ public void StringToUInt32_Invalid(string jsonValue)
+ {
+ string json = "{ \"singleUint32\": \"" + jsonValue + "\"}";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("0", 0L)]
+ [TestCase("1", 1L)]
+ [TestCase("-1", -1L)]
+ [TestCase("9223372036854775807", 9223372036854775807)]
+ [TestCase("-9223372036854775808", -9223372036854775808)]
+ public void StringToInt64_Valid(string jsonValue, long expectedParsedValue)
+ {
+ string json = "{ \"singleInt64\": \"" + jsonValue + "\"}";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(expectedParsedValue, parsed.SingleInt64);
+ }
+
+ // Assume that anything non-bounds-related is covered in the Int32 case
+ [Test]
+ [TestCase("-9223372036854775809")]
+ [TestCase("9223372036854775808")]
+ public void StringToInt64_Invalid(string jsonValue)
+ {
+ string json = "{ \"singleInt64\": \"" + jsonValue + "\"}";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("0", 0UL)]
+ [TestCase("1", 1UL)]
+ [TestCase("18446744073709551615", 18446744073709551615)]
+ public void StringToUInt64_Valid(string jsonValue, ulong expectedParsedValue)
+ {
+ string json = "{ \"singleUint64\": \"" + jsonValue + "\"}";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(expectedParsedValue, parsed.SingleUint64);
+ }
+
+ // Assume that anything non-bounds-related is covered in the Int32 case
+ [Test]
+ [TestCase("-1")]
+ [TestCase("18446744073709551616")]
+ public void StringToUInt64_Invalid(string jsonValue)
+ {
+ string json = "{ \"singleUint64\": \"" + jsonValue + "\"}";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("0", 0d)]
+ [TestCase("1", 1d)]
+ [TestCase("1.000000", 1d)]
+ [TestCase("1.0000000000000000000000001", 1d)] // We don't notice that we haven't preserved the exact value
+ [TestCase("-1", -1d)]
+ [TestCase("1e1", 10d)]
+ [TestCase("1e01", 10d)] // Leading decimals are allowed in exponents
+ [TestCase("1E1", 10d)] // Either case is fine
+ [TestCase("-1e1", -10d)]
+ [TestCase("1.5e1", 15d)]
+ [TestCase("-1.5e1", -15d)]
+ [TestCase("15e-1", 1.5d)]
+ [TestCase("-15e-1", -1.5d)]
+ [TestCase("1.79769e308", 1.79769e308)]
+ [TestCase("-1.79769e308", -1.79769e308)]
+ [TestCase("Infinity", double.PositiveInfinity)]
+ [TestCase("-Infinity", double.NegativeInfinity)]
+ [TestCase("NaN", double.NaN)]
+ public void StringToDouble_Valid(string jsonValue, double expectedParsedValue)
+ {
+ string json = "{ \"singleDouble\": \"" + jsonValue + "\"}";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(expectedParsedValue, parsed.SingleDouble);
+ }
+
+ [Test]
+ [TestCase("1.7977e308")]
+ [TestCase("-1.7977e308")]
+ [TestCase("1e309")]
+ [TestCase("1,0")]
+ [TestCase("1.0.0")]
+ [TestCase("+1")]
+ [TestCase("00")]
+ [TestCase("01")]
+ [TestCase("-00")]
+ [TestCase("-01")]
+ [TestCase("--1")]
+ [TestCase(" Infinity")]
+ [TestCase(" -Infinity")]
+ [TestCase("NaN ")]
+ [TestCase("Infinity ")]
+ [TestCase("-Infinity ")]
+ [TestCase(" NaN")]
+ [TestCase("INFINITY")]
+ [TestCase("nan")]
+ [TestCase("\u00BD")] // 1/2 as a single Unicode character. Just sanity checking...
+ public void StringToDouble_Invalid(string jsonValue)
+ {
+ string json = "{ \"singleDouble\": \"" + jsonValue + "\"}";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("0", 0f)]
+ [TestCase("1", 1f)]
+ [TestCase("1.000000", 1f)]
+ [TestCase("-1", -1f)]
+ [TestCase("3.402823e38", 3.402823e38f)]
+ [TestCase("-3.402823e38", -3.402823e38f)]
+ [TestCase("1.5e1", 15f)]
+ [TestCase("15e-1", 1.5f)]
+ public void StringToFloat_Valid(string jsonValue, float expectedParsedValue)
+ {
+ string json = "{ \"singleFloat\": \"" + jsonValue + "\"}";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(expectedParsedValue, parsed.SingleFloat);
+ }
+
+ [Test]
+ [TestCase("3.402824e38")]
+ [TestCase("-3.402824e38")]
+ [TestCase("1,0")]
+ [TestCase("1.0.0")]
+ [TestCase("+1")]
+ [TestCase("00")]
+ [TestCase("--1")]
+ public void StringToFloat_Invalid(string jsonValue)
+ {
+ string json = "{ \"singleFloat\": \"" + jsonValue + "\"}";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("0", 0)]
+ [TestCase("-0", 0)] // Not entirely clear whether we intend to allow this...
+ [TestCase("1", 1)]
+ [TestCase("-1", -1)]
+ [TestCase("2147483647", 2147483647)]
+ [TestCase("-2147483648", -2147483648)]
+ [TestCase("1e1", 10)]
+ [TestCase("-1e1", -10)]
+ [TestCase("10.00", 10)]
+ [TestCase("-10.00", -10)]
+ public void NumberToInt32_Valid(string jsonValue, int expectedParsedValue)
+ {
+ string json = "{ \"singleInt32\": " + jsonValue + "}";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(expectedParsedValue, parsed.SingleInt32);
+ }
+
+ [Test]
+ [TestCase("+0", typeof(InvalidJsonException))]
+ [TestCase("00", typeof(InvalidJsonException))]
+ [TestCase("-00", typeof(InvalidJsonException))]
+ [TestCase("--1", typeof(InvalidJsonException))]
+ [TestCase("+1", typeof(InvalidJsonException))]
+ [TestCase("1.5", typeof(InvalidProtocolBufferException))]
+ // Value is out of range
+ [TestCase("1e10", typeof(InvalidProtocolBufferException))]
+ [TestCase("2147483648", typeof(InvalidProtocolBufferException))]
+ [TestCase("-2147483649", typeof(InvalidProtocolBufferException))]
+ public void NumberToInt32_Invalid(string jsonValue, System.Type expectedExceptionType)
+ {
+ string json = "{ \"singleInt32\": " + jsonValue + "}";
+ Assert.Throws(expectedExceptionType, () => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("0", 0U)]
+ [TestCase("1", 1U)]
+ [TestCase("4294967295", 4294967295U)]
+ public void NumberToUInt32_Valid(string jsonValue, uint expectedParsedValue)
+ {
+ string json = "{ \"singleUint32\": " + jsonValue + "}";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(expectedParsedValue, parsed.SingleUint32);
+ }
+
+ // Assume that anything non-bounds-related is covered in the Int32 case
+ [Test]
+ [TestCase("-1")]
+ [TestCase("4294967296")]
+ public void NumberToUInt32_Invalid(string jsonValue)
+ {
+ string json = "{ \"singleUint32\": " + jsonValue + "}";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("0", 0L)]
+ [TestCase("1", 1L)]
+ [TestCase("-1", -1L)]
+ // long.MaxValue isn't actually representable as a double. This string value is the highest
+ // representable value which isn't greater than long.MaxValue.
+ [TestCase("9223372036854774784", 9223372036854774784)]
+ [TestCase("-9223372036854775808", -9223372036854775808)]
+ public void NumberToInt64_Valid(string jsonValue, long expectedParsedValue)
+ {
+ string json = "{ \"singleInt64\": " + jsonValue + "}";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(expectedParsedValue, parsed.SingleInt64);
+ }
+
+ // Assume that anything non-bounds-related is covered in the Int32 case
+ [Test]
+ [TestCase("9223372036854775808")]
+ // Theoretical bound would be -9223372036854775809, but when that is parsed to a double
+ // we end up with the exact value of long.MinValue due to lack of precision. The value here
+ // is the "next double down".
+ [TestCase("-9223372036854780000")]
+ public void NumberToInt64_Invalid(string jsonValue)
+ {
+ string json = "{ \"singleInt64\": " + jsonValue + "}";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("0", 0UL)]
+ [TestCase("1", 1UL)]
+ // ulong.MaxValue isn't representable as a double. This value is the largest double within
+ // the range of ulong.
+ [TestCase("18446744073709549568", 18446744073709549568UL)]
+ public void NumberToUInt64_Valid(string jsonValue, ulong expectedParsedValue)
+ {
+ string json = "{ \"singleUint64\": " + jsonValue + "}";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(expectedParsedValue, parsed.SingleUint64);
+ }
+
+ // Assume that anything non-bounds-related is covered in the Int32 case
+ [Test]
+ [TestCase("-1")]
+ [TestCase("18446744073709551616")]
+ public void NumberToUInt64_Invalid(string jsonValue)
+ {
+ string json = "{ \"singleUint64\": " + jsonValue + "}";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("0", 0d)]
+ [TestCase("1", 1d)]
+ [TestCase("1.000000", 1d)]
+ [TestCase("1.0000000000000000000000001", 1d)] // We don't notice that we haven't preserved the exact value
+ [TestCase("-1", -1d)]
+ [TestCase("1e1", 10d)]
+ [TestCase("1e01", 10d)] // Leading decimals are allowed in exponents
+ [TestCase("1E1", 10d)] // Either case is fine
+ [TestCase("-1e1", -10d)]
+ [TestCase("1.5e1", 15d)]
+ [TestCase("-1.5e1", -15d)]
+ [TestCase("15e-1", 1.5d)]
+ [TestCase("-15e-1", -1.5d)]
+ [TestCase("1.79769e308", 1.79769e308)]
+ [TestCase("-1.79769e308", -1.79769e308)]
+ public void NumberToDouble_Valid(string jsonValue, double expectedParsedValue)
+ {
+ string json = "{ \"singleDouble\": " + jsonValue + "}";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(expectedParsedValue, parsed.SingleDouble);
+ }
+
+ [Test]
+ [TestCase("1.7977e308")]
+ [TestCase("-1.7977e308")]
+ [TestCase("1e309")]
+ [TestCase("1,0")]
+ [TestCase("1.0.0")]
+ [TestCase("+1")]
+ [TestCase("00")]
+ [TestCase("--1")]
+ [TestCase("\u00BD")] // 1/2 as a single Unicode character. Just sanity checking...
+ public void NumberToDouble_Invalid(string jsonValue)
+ {
+ string json = "{ \"singleDouble\": " + jsonValue + "}";
+ Assert.Throws<InvalidJsonException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("0", 0f)]
+ [TestCase("1", 1f)]
+ [TestCase("1.000000", 1f)]
+ [TestCase("-1", -1f)]
+ [TestCase("3.402823e38", 3.402823e38f)]
+ [TestCase("-3.402823e38", -3.402823e38f)]
+ [TestCase("1.5e1", 15f)]
+ [TestCase("15e-1", 1.5f)]
+ public void NumberToFloat_Valid(string jsonValue, float expectedParsedValue)
+ {
+ string json = "{ \"singleFloat\": " + jsonValue + "}";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(expectedParsedValue, parsed.SingleFloat);
+ }
+
+ [Test]
+ [TestCase("3.402824e38", typeof(InvalidProtocolBufferException))]
+ [TestCase("-3.402824e38", typeof(InvalidProtocolBufferException))]
+ [TestCase("1,0", typeof(InvalidJsonException))]
+ [TestCase("1.0.0", typeof(InvalidJsonException))]
+ [TestCase("+1", typeof(InvalidJsonException))]
+ [TestCase("00", typeof(InvalidJsonException))]
+ [TestCase("--1", typeof(InvalidJsonException))]
+ public void NumberToFloat_Invalid(string jsonValue, System.Type expectedExceptionType)
+ {
+ string json = "{ \"singleFloat\": " + jsonValue + "}";
+ Assert.Throws(expectedExceptionType, () => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ // The simplest way of testing that the value has parsed correctly is to reformat it,
+ // as we trust the formatting. In many cases that will give the same result as the input,
+ // so in those cases we accept an expectedFormatted value of null. Sometimes the results
+ // will be different though, due to a different number of digits being provided.
+ [Test]
+ // Z offset
+ [TestCase("2015-10-09T14:46:23.123456789Z", null)]
+ [TestCase("2015-10-09T14:46:23.123456Z", null)]
+ [TestCase("2015-10-09T14:46:23.123Z", null)]
+ [TestCase("2015-10-09T14:46:23Z", null)]
+ [TestCase("2015-10-09T14:46:23.123456000Z", "2015-10-09T14:46:23.123456Z")]
+ [TestCase("2015-10-09T14:46:23.1234560Z", "2015-10-09T14:46:23.123456Z")]
+ [TestCase("2015-10-09T14:46:23.123000000Z", "2015-10-09T14:46:23.123Z")]
+ [TestCase("2015-10-09T14:46:23.1230Z", "2015-10-09T14:46:23.123Z")]
+ [TestCase("2015-10-09T14:46:23.00Z", "2015-10-09T14:46:23Z")]
+
+ // +00:00 offset
+ [TestCase("2015-10-09T14:46:23.123456789+00:00", "2015-10-09T14:46:23.123456789Z")]
+ [TestCase("2015-10-09T14:46:23.123456+00:00", "2015-10-09T14:46:23.123456Z")]
+ [TestCase("2015-10-09T14:46:23.123+00:00", "2015-10-09T14:46:23.123Z")]
+ [TestCase("2015-10-09T14:46:23+00:00", "2015-10-09T14:46:23Z")]
+ [TestCase("2015-10-09T14:46:23.123456000+00:00", "2015-10-09T14:46:23.123456Z")]
+ [TestCase("2015-10-09T14:46:23.1234560+00:00", "2015-10-09T14:46:23.123456Z")]
+ [TestCase("2015-10-09T14:46:23.123000000+00:00", "2015-10-09T14:46:23.123Z")]
+ [TestCase("2015-10-09T14:46:23.1230+00:00", "2015-10-09T14:46:23.123Z")]
+ [TestCase("2015-10-09T14:46:23.00+00:00", "2015-10-09T14:46:23Z")]
+
+ // Other offsets (assume by now that the subsecond handling is okay)
+ [TestCase("2015-10-09T15:46:23.123456789+01:00", "2015-10-09T14:46:23.123456789Z")]
+ [TestCase("2015-10-09T13:46:23.123456789-01:00", "2015-10-09T14:46:23.123456789Z")]
+ [TestCase("2015-10-09T15:16:23.123456789+00:30", "2015-10-09T14:46:23.123456789Z")]
+ [TestCase("2015-10-09T14:16:23.123456789-00:30", "2015-10-09T14:46:23.123456789Z")]
+ [TestCase("2015-10-09T16:31:23.123456789+01:45", "2015-10-09T14:46:23.123456789Z")]
+ [TestCase("2015-10-09T13:01:23.123456789-01:45", "2015-10-09T14:46:23.123456789Z")]
+ [TestCase("2015-10-10T08:46:23.123456789+18:00", "2015-10-09T14:46:23.123456789Z")]
+ [TestCase("2015-10-08T20:46:23.123456789-18:00", "2015-10-09T14:46:23.123456789Z")]
+
+ // Leap years and min/max
+ [TestCase("2016-02-29T14:46:23.123456789Z", null)]
+ [TestCase("2000-02-29T14:46:23.123456789Z", null)]
+ [TestCase("0001-01-01T00:00:00Z", null)]
+ [TestCase("9999-12-31T23:59:59.999999999Z", null)]
+ public void Timestamp_Valid(string jsonValue, string expectedFormatted)
+ {
+ expectedFormatted = expectedFormatted ?? jsonValue;
+ string json = WrapInQuotes(jsonValue);
+ var parsed = Timestamp.Parser.ParseJson(json);
+ Assert.AreEqual(WrapInQuotes(expectedFormatted), parsed.ToString());
+ }
+
+ [Test]
+ [TestCase("2015-10-09 14:46:23.123456789Z", Description = "No T between date and time")]
+ [TestCase("2015/10/09T14:46:23.123456789Z", Description = "Wrong date separators")]
+ [TestCase("2015-10-09T14.46.23.123456789Z", Description = "Wrong time separators")]
+ [TestCase("2015-10-09T14:46:23,123456789Z", Description = "Wrong fractional second separators (valid ISO-8601 though)")]
+ [TestCase(" 2015-10-09T14:46:23.123456789Z", Description = "Whitespace at start")]
+ [TestCase("2015-10-09T14:46:23.123456789Z ", Description = "Whitespace at end")]
+ [TestCase("2015-10-09T14:46:23.1234567890", Description = "Too many digits")]
+ [TestCase("2015-10-09T14:46:23.123456789", Description = "No offset")]
+ [TestCase("2015-13-09T14:46:23.123456789Z", Description = "Invalid month")]
+ [TestCase("2015-10-32T14:46:23.123456789Z", Description = "Invalid day")]
+ [TestCase("2015-10-09T24:00:00.000000000Z", Description = "Invalid hour (valid ISO-8601 though)")]
+ [TestCase("2015-10-09T14:60:23.123456789Z", Description = "Invalid minutes")]
+ [TestCase("2015-10-09T14:46:60.123456789Z", Description = "Invalid seconds")]
+ [TestCase("2015-10-09T14:46:23.123456789+18:01", Description = "Offset too large (positive)")]
+ [TestCase("2015-10-09T14:46:23.123456789-18:01", Description = "Offset too large (negative)")]
+ [TestCase("2015-10-09T14:46:23.123456789-00:00", Description = "Local offset (-00:00) makes no sense here")]
+ [TestCase("0001-01-01T00:00:00+00:01", Description = "Value before earliest when offset applied")]
+ [TestCase("9999-12-31T23:59:59.999999999-00:01", Description = "Value after latest when offset applied")]
+ [TestCase("2100-02-29T14:46:23.123456789Z", Description = "Feb 29th on a non-leap-year")]
+ public void Timestamp_Invalid(string jsonValue)
+ {
+ string json = WrapInQuotes(jsonValue);
+ Assert.Throws<InvalidProtocolBufferException>(() => Timestamp.Parser.ParseJson(json));
+ }
+
+ [Test]
+ public void StructValue_Null()
+ {
+ Assert.AreEqual(new Value { NullValue = 0 }, Value.Parser.ParseJson("null"));
+ }
+
+ [Test]
+ public void StructValue_String()
+ {
+ Assert.AreEqual(new Value { StringValue = "hi" }, Value.Parser.ParseJson("\"hi\""));
+ }
+
+ [Test]
+ public void StructValue_Bool()
+ {
+ Assert.AreEqual(new Value { BoolValue = true }, Value.Parser.ParseJson("true"));
+ Assert.AreEqual(new Value { BoolValue = false }, Value.Parser.ParseJson("false"));
+ }
+
+ [Test]
+ public void StructValue_List()
+ {
+ Assert.AreEqual(Value.ForList(Value.ForNumber(1), Value.ForString("x")), Value.Parser.ParseJson("[1, \"x\"]"));
+ }
+
+ [Test]
+ public void ParseListValue()
+ {
+ Assert.AreEqual(new ListValue { Values = { Value.ForNumber(1), Value.ForString("x") } }, ListValue.Parser.ParseJson("[1, \"x\"]"));
+ }
+
+ [Test]
+ public void StructValue_Struct()
+ {
+ Assert.AreEqual(
+ Value.ForStruct(new Struct { Fields = { { "x", Value.ForNumber(1) }, { "y", Value.ForString("z") } } }),
+ Value.Parser.ParseJson("{ \"x\": 1, \"y\": \"z\" }"));
+ }
+
+ [Test]
+ public void ParseStruct()
+ {
+ Assert.AreEqual(new Struct { Fields = { { "x", Value.ForNumber(1) }, { "y", Value.ForString("z") } } },
+ Struct.Parser.ParseJson("{ \"x\": 1, \"y\": \"z\" }"));
+ }
+
+ // TODO for duration parsing: upper and lower bounds.
+ // +/- 315576000000 seconds
+
+ [Test]
+ [TestCase("1.123456789s", null)]
+ [TestCase("1.123456s", null)]
+ [TestCase("1.123s", null)]
+ [TestCase("1.12300s", "1.123s")]
+ [TestCase("1.12345s", "1.123450s")]
+ [TestCase("1s", null)]
+ [TestCase("-1.123456789s", null)]
+ [TestCase("-1.123456s", null)]
+ [TestCase("-1.123s", null)]
+ [TestCase("-1s", null)]
+ [TestCase("0.123s", null)]
+ [TestCase("-0.123s", null)]
+ [TestCase("123456.123s", null)]
+ [TestCase("-123456.123s", null)]
+ // Upper and lower bounds
+ [TestCase("315576000000s", null)]
+ [TestCase("-315576000000s", null)]
+ public void Duration_Valid(string jsonValue, string expectedFormatted)
+ {
+ expectedFormatted = expectedFormatted ?? jsonValue;
+ string json = WrapInQuotes(jsonValue);
+ var parsed = Duration.Parser.ParseJson(json);
+ Assert.AreEqual(WrapInQuotes(expectedFormatted), parsed.ToString());
+ }
+
+ // The simplest way of testing that the value has parsed correctly is to reformat it,
+ // as we trust the formatting. In many cases that will give the same result as the input,
+ // so in those cases we accept an expectedFormatted value of null. Sometimes the results
+ // will be different though, due to a different number of digits being provided.
+ [Test]
+ [TestCase("1.1234567890s", Description = "Too many digits")]
+ [TestCase("1.123456789", Description = "No suffix")]
+ [TestCase("1.123456789ss", Description = "Too much suffix")]
+ [TestCase("1.123456789S", Description = "Upper case suffix")]
+ [TestCase("+1.123456789s", Description = "Leading +")]
+ [TestCase(".123456789s", Description = "No integer before the fraction")]
+ [TestCase("1,123456789s", Description = "Comma as decimal separator")]
+ [TestCase("1x1.123456789s", Description = "Non-digit in integer part")]
+ [TestCase("1.1x3456789s", Description = "Non-digit in fractional part")]
+ [TestCase(" 1.123456789s", Description = "Whitespace before fraction")]
+ [TestCase("1.123456789s ", Description = "Whitespace after value")]
+ [TestCase("01.123456789s", Description = "Leading zero (positive)")]
+ [TestCase("-01.123456789s", Description = "Leading zero (negative)")]
+ [TestCase("--0.123456789s", Description = "Double minus sign")]
+ // Violate upper/lower bounds in various ways
+ [TestCase("315576000001s", Description = "Integer part too large")]
+ [TestCase("3155760000000s", Description = "Integer part too long (positive)")]
+ [TestCase("-3155760000000s", Description = "Integer part too long (negative)")]
+ public void Duration_Invalid(string jsonValue)
+ {
+ string json = WrapInQuotes(jsonValue);
+ Assert.Throws<InvalidProtocolBufferException>(() => Duration.Parser.ParseJson(json));
+ }
+
+ // Not as many tests for field masks as I'd like; more to be added when we have more
+ // detailed specifications.
+
+ [Test]
+ [TestCase("")]
+ [TestCase("foo", "foo")]
+ [TestCase("foo,bar", "foo", "bar")]
+ [TestCase("foo.bar", "foo.bar")]
+ [TestCase("fooBar", "foo_bar")]
+ [TestCase("fooBar.bazQux", "foo_bar.baz_qux")]
+ public void FieldMask_Valid(string jsonValue, params string[] expectedPaths)
+ {
+ string json = WrapInQuotes(jsonValue);
+ var parsed = FieldMask.Parser.ParseJson(json);
+ CollectionAssert.AreEqual(expectedPaths, parsed.Paths);
+ }
+
+ [Test]
+ [TestCase("foo_bar")]
+ public void FieldMask_Invalid(string jsonValue)
+ {
+ string json = WrapInQuotes(jsonValue);
+ Assert.Throws<InvalidProtocolBufferException>(() => FieldMask.Parser.ParseJson(json));
+ }
+
+ [Test]
+ public void Any_RegularMessage()
+ {
+ var registry = TypeRegistry.FromMessages(TestAllTypes.Descriptor);
+ var formatter = new JsonFormatter(new JsonFormatter.Settings(false, TypeRegistry.FromMessages(TestAllTypes.Descriptor)));
+ var message = new TestAllTypes { SingleInt32 = 10, SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 20 } };
+ var original = Any.Pack(message);
+ var json = formatter.Format(original); // This is tested in JsonFormatterTest
+ var parser = new JsonParser(new JsonParser.Settings(10, registry));
+ Assert.AreEqual(original, parser.Parse<Any>(json));
+ string valueFirstJson = "{ \"singleInt32\": 10, \"singleNestedMessage\": { \"bb\": 20 }, \"@type\": \"type.googleapis.com/protobuf_unittest.TestAllTypes\" }";
+ Assert.AreEqual(original, parser.Parse<Any>(valueFirstJson));
+ }
+
+ [Test]
+ public void Any_CustomPrefix()
+ {
+ var registry = TypeRegistry.FromMessages(TestAllTypes.Descriptor);
+ var message = new TestAllTypes { SingleInt32 = 10 };
+ var original = Any.Pack(message, "custom.prefix/middle-part");
+ var parser = new JsonParser(new JsonParser.Settings(10, registry));
+ string json = "{ \"@type\": \"custom.prefix/middle-part/protobuf_unittest.TestAllTypes\", \"singleInt32\": 10 }";
+ Assert.AreEqual(original, parser.Parse<Any>(json));
+ }
+
+ [Test]
+ public void Any_UnknownType()
+ {
+ string json = "{ \"@type\": \"type.googleapis.com/bogus\" }";
+ Assert.Throws<InvalidOperationException>(() => Any.Parser.ParseJson(json));
+ }
+
+ [Test]
+ public void Any_NoTypeUrl()
+ {
+ string json = "{ \"foo\": \"bar\" }";
+ Assert.Throws<InvalidProtocolBufferException>(() => Any.Parser.ParseJson(json));
+ }
+
+ [Test]
+ public void Any_WellKnownType()
+ {
+ var registry = TypeRegistry.FromMessages(Timestamp.Descriptor);
+ var formatter = new JsonFormatter(new JsonFormatter.Settings(false, registry));
+ var timestamp = new DateTime(1673, 6, 19, 12, 34, 56, DateTimeKind.Utc).ToTimestamp();
+ var original = Any.Pack(timestamp);
+ var json = formatter.Format(original); // This is tested in JsonFormatterTest
+ var parser = new JsonParser(new JsonParser.Settings(10, registry));
+ Assert.AreEqual(original, parser.Parse<Any>(json));
+ string valueFirstJson = "{ \"value\": \"1673-06-19T12:34:56Z\", \"@type\": \"type.googleapis.com/google.protobuf.Timestamp\" }";
+ Assert.AreEqual(original, parser.Parse<Any>(valueFirstJson));
+ }
+
+ [Test]
+ public void Any_Nested()
+ {
+ var registry = TypeRegistry.FromMessages(TestWellKnownTypes.Descriptor, TestAllTypes.Descriptor);
+ var formatter = new JsonFormatter(new JsonFormatter.Settings(false, registry));
+ var parser = new JsonParser(new JsonParser.Settings(10, registry));
+ var doubleNestedMessage = new TestAllTypes { SingleInt32 = 20 };
+ var nestedMessage = Any.Pack(doubleNestedMessage);
+ var message = new TestWellKnownTypes { AnyField = Any.Pack(nestedMessage) };
+ var json = formatter.Format(message);
+ // Use the descriptor-based parser just for a change.
+ Assert.AreEqual(message, parser.Parse(json, TestWellKnownTypes.Descriptor));
+ }
+
+ [Test]
+ public void DataAfterObject()
+ {
+ string json = "{} 10";
+ Assert.Throws<InvalidJsonException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ /// <summary>
+ /// JSON equivalent to <see cref="CodedInputStreamTest.MaliciousRecursion"/>
+ /// </summary>
+ [Test]
+ public void MaliciousRecursion()
+ {
+ string data64 = CodedInputStreamTest.MakeRecursiveMessage(64).ToString();
+ string data65 = CodedInputStreamTest.MakeRecursiveMessage(65).ToString();
+
+ var parser64 = new JsonParser(new JsonParser.Settings(64));
+ CodedInputStreamTest.AssertMessageDepth(parser64.Parse<TestRecursiveMessage>(data64), 64);
+ Assert.Throws<InvalidProtocolBufferException>(() => parser64.Parse<TestRecursiveMessage>(data65));
+
+ var parser63 = new JsonParser(new JsonParser.Settings(63));
+ Assert.Throws<InvalidProtocolBufferException>(() => parser63.Parse<TestRecursiveMessage>(data64));
+ }
+
+ [Test]
+ [TestCase("AQI")]
+ [TestCase("_-==")]
+ public void Bytes_InvalidBase64(string badBase64)
+ {
+ string json = "{ \"singleBytes\": \"" + badBase64 + "\" }";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ [TestCase("\"FOREIGN_BAR\"", ForeignEnum.ForeignBar)]
+ [TestCase("5", ForeignEnum.ForeignBar)]
+ [TestCase("100", (ForeignEnum)100)]
+ public void EnumValid(string value, ForeignEnum expectedValue)
+ {
+ string json = "{ \"singleForeignEnum\": " + value + " }";
+ var parsed = TestAllTypes.Parser.ParseJson(json);
+ Assert.AreEqual(new TestAllTypes { SingleForeignEnum = expectedValue }, parsed);
+ }
+
+ [Test]
+ [TestCase("\"NOT_A_VALID_VALUE\"")]
+ [TestCase("5.5")]
+ public void Enum_Invalid(string value)
+ {
+ string json = "{ \"singleForeignEnum\": " + value + " }";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ [Test]
+ public void OneofDuplicate_Invalid()
+ {
+ string json = "{ \"oneofString\": \"x\", \"oneofUint32\": 10 }";
+ Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
+ }
+
+ /// <summary>
+ /// Various tests use strings which have quotes round them for parsing or as the result
+ /// of formatting, but without those quotes being specified in the tests (for the sake of readability).
+ /// This method simply returns the input, wrapped in double quotes.
+ /// </summary>
+ internal static string WrapInQuotes(string text)
+ {
+ return '"' + text + '"';
+ }
+ }
+} \ No newline at end of file
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/JsonTokenizerTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/JsonTokenizerTest.cs
new file mode 100644
index 00000000..527ab336
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/JsonTokenizerTest.cs
@@ -0,0 +1,408 @@
+#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 NUnit.Framework;
+using System;
+using System.IO;
+
+namespace Google.Protobuf
+{
+ public class JsonTokenizerTest
+ {
+ [Test]
+ public void EmptyObjectValue()
+ {
+ AssertTokens("{}", JsonToken.StartObject, JsonToken.EndObject);
+ }
+
+ [Test]
+ public void EmptyArrayValue()
+ {
+ AssertTokens("[]", JsonToken.StartArray, JsonToken.EndArray);
+ }
+
+ [Test]
+ [TestCase("foo", "foo")]
+ [TestCase("tab\\t", "tab\t")]
+ [TestCase("line\\nfeed", "line\nfeed")]
+ [TestCase("carriage\\rreturn", "carriage\rreturn")]
+ [TestCase("back\\bspace", "back\bspace")]
+ [TestCase("form\\ffeed", "form\ffeed")]
+ [TestCase("escaped\\/slash", "escaped/slash")]
+ [TestCase("escaped\\\\backslash", "escaped\\backslash")]
+ [TestCase("escaped\\\"quote", "escaped\"quote")]
+ [TestCase("foo {}[] bar", "foo {}[] bar")]
+ [TestCase("foo\\u09aFbar", "foo\u09afbar")] // Digits, upper hex, lower hex
+ [TestCase("ab\ud800\udc00cd", "ab\ud800\udc00cd")]
+ [TestCase("ab\\ud800\\udc00cd", "ab\ud800\udc00cd")]
+ public void StringValue(string json, string expectedValue)
+ {
+ AssertTokensNoReplacement("\"" + json + "\"", JsonToken.Value(expectedValue));
+ }
+
+ // Valid surrogate pairs, with mixed escaping. These test cases can't be expressed
+ // using TestCase as they have no valid UTF-8 representation.
+ // It's unclear exactly how we should handle a mixture of escaped or not: that can't
+ // come from UTF-8 text, but could come from a .NET string. For the moment,
+ // treat it as valid in the obvious way.
+ [Test]
+ public void MixedSurrogatePairs()
+ {
+ string expected = "\ud800\udc00";
+ AssertTokens("'\\ud800\udc00'", JsonToken.Value(expected));
+ AssertTokens("'\ud800\\udc00'", JsonToken.Value(expected));
+ }
+
+ [Test]
+ public void ObjectDepth()
+ {
+ string json = "{ \"foo\": { \"x\": 1, \"y\": [ 0 ] } }";
+ var tokenizer = JsonTokenizer.FromTextReader(new StringReader(json));
+ // If we had more tests like this, I'd introduce a helper method... but for one test, it's not worth it.
+ Assert.AreEqual(0, tokenizer.ObjectDepth);
+ Assert.AreEqual(JsonToken.StartObject, tokenizer.Next());
+ Assert.AreEqual(1, tokenizer.ObjectDepth);
+ Assert.AreEqual(JsonToken.Name("foo"), tokenizer.Next());
+ Assert.AreEqual(1, tokenizer.ObjectDepth);
+ Assert.AreEqual(JsonToken.StartObject, tokenizer.Next());
+ Assert.AreEqual(2, tokenizer.ObjectDepth);
+ Assert.AreEqual(JsonToken.Name("x"), tokenizer.Next());
+ Assert.AreEqual(2, tokenizer.ObjectDepth);
+ Assert.AreEqual(JsonToken.Value(1), tokenizer.Next());
+ Assert.AreEqual(2, tokenizer.ObjectDepth);
+ Assert.AreEqual(JsonToken.Name("y"), tokenizer.Next());
+ Assert.AreEqual(2, tokenizer.ObjectDepth);
+ Assert.AreEqual(JsonToken.StartArray, tokenizer.Next());
+ Assert.AreEqual(2, tokenizer.ObjectDepth); // Depth hasn't changed in array
+ Assert.AreEqual(JsonToken.Value(0), tokenizer.Next());
+ Assert.AreEqual(2, tokenizer.ObjectDepth);
+ Assert.AreEqual(JsonToken.EndArray, tokenizer.Next());
+ Assert.AreEqual(2, tokenizer.ObjectDepth);
+ Assert.AreEqual(JsonToken.EndObject, tokenizer.Next());
+ Assert.AreEqual(1, tokenizer.ObjectDepth);
+ Assert.AreEqual(JsonToken.EndObject, tokenizer.Next());
+ Assert.AreEqual(0, tokenizer.ObjectDepth);
+ Assert.AreEqual(JsonToken.EndDocument, tokenizer.Next());
+ Assert.AreEqual(0, tokenizer.ObjectDepth);
+ }
+
+ [Test]
+ public void ObjectDepth_WithPushBack()
+ {
+ string json = "{}";
+ var tokenizer = JsonTokenizer.FromTextReader(new StringReader(json));
+ Assert.AreEqual(0, tokenizer.ObjectDepth);
+ var token = tokenizer.Next();
+ Assert.AreEqual(1, tokenizer.ObjectDepth);
+ // When we push back a "start object", we should effectively be back to the previous depth.
+ tokenizer.PushBack(token);
+ Assert.AreEqual(0, tokenizer.ObjectDepth);
+ // Read the same token again, and get back to depth 1
+ token = tokenizer.Next();
+ Assert.AreEqual(1, tokenizer.ObjectDepth);
+
+ // Now the same in reverse, with EndObject
+ token = tokenizer.Next();
+ Assert.AreEqual(0, tokenizer.ObjectDepth);
+ tokenizer.PushBack(token);
+ Assert.AreEqual(1, tokenizer.ObjectDepth);
+ tokenizer.Next();
+ Assert.AreEqual(0, tokenizer.ObjectDepth);
+ }
+
+ [Test]
+ [TestCase("embedded tab\t")]
+ [TestCase("embedded CR\r")]
+ [TestCase("embedded LF\n")]
+ [TestCase("embedded bell\u0007")]
+ [TestCase("bad escape\\a")]
+ [TestCase("incomplete escape\\")]
+ [TestCase("incomplete Unicode escape\\u000")]
+ [TestCase("invalid Unicode escape\\u000H")]
+ // Surrogate pair handling, both in raw .NET strings and escaped. We only need
+ // to detect this in strings, as non-ASCII characters anywhere other than in strings
+ // will already lead to parsing errors.
+ [TestCase("\\ud800")]
+ [TestCase("\\udc00")]
+ [TestCase("\\ud800x")]
+ [TestCase("\\udc00x")]
+ [TestCase("\\udc00\\ud800y")]
+ public void InvalidStringValue(string json)
+ {
+ AssertThrowsAfter("\"" + json + "\"");
+ }
+
+ // Tests for invalid strings that can't be expressed in attributes,
+ // as the constants can't be expressed as UTF-8 strings.
+ [Test]
+ public void InvalidSurrogatePairs()
+ {
+ AssertThrowsAfter("\"\ud800x\"");
+ AssertThrowsAfter("\"\udc00y\"");
+ AssertThrowsAfter("\"\udc00\ud800y\"");
+ }
+
+ [Test]
+ [TestCase("0", 0)]
+ [TestCase("-0", 0)] // We don't distinguish between positive and negative 0
+ [TestCase("1", 1)]
+ [TestCase("-1", -1)]
+ // From here on, assume leading sign is okay...
+ [TestCase("1.125", 1.125)]
+ [TestCase("1.0", 1)]
+ [TestCase("1e5", 100000)]
+ [TestCase("1e000000", 1)] // Weird, but not prohibited by the spec
+ [TestCase("1E5", 100000)]
+ [TestCase("1e+5", 100000)]
+ [TestCase("1E-5", 0.00001)]
+ [TestCase("123E-2", 1.23)]
+ [TestCase("123.45E3", 123450)]
+ [TestCase(" 1 ", 1)]
+ public void NumberValue(string json, double expectedValue)
+ {
+ AssertTokens(json, JsonToken.Value(expectedValue));
+ }
+
+ [Test]
+ [TestCase("00")]
+ [TestCase(".5")]
+ [TestCase("1.")]
+ [TestCase("1e")]
+ [TestCase("1e-")]
+ [TestCase("--")]
+ [TestCase("--1")]
+ [TestCase("-1.7977e308")]
+ [TestCase("1.7977e308")]
+ public void InvalidNumberValue(string json)
+ {
+ AssertThrowsAfter(json);
+ }
+
+ [Test]
+ [TestCase("nul")]
+ [TestCase("nothing")]
+ [TestCase("truth")]
+ [TestCase("fALSEhood")]
+ public void InvalidLiterals(string json)
+ {
+ AssertThrowsAfter(json);
+ }
+
+ [Test]
+ public void NullValue()
+ {
+ AssertTokens("null", JsonToken.Null);
+ }
+
+ [Test]
+ public void TrueValue()
+ {
+ AssertTokens("true", JsonToken.True);
+ }
+
+ [Test]
+ public void FalseValue()
+ {
+ AssertTokens("false", JsonToken.False);
+ }
+
+ [Test]
+ public void SimpleObject()
+ {
+ AssertTokens("{'x': 'y'}",
+ JsonToken.StartObject, JsonToken.Name("x"), JsonToken.Value("y"), JsonToken.EndObject);
+ }
+
+ [Test]
+ [TestCase("[10, 20", 3)]
+ [TestCase("[10,", 2)]
+ [TestCase("[10:20]", 2)]
+ [TestCase("[", 1)]
+ [TestCase("[,", 1)]
+ [TestCase("{", 1)]
+ [TestCase("{,", 1)]
+ [TestCase("{[", 1)]
+ [TestCase("{{", 1)]
+ [TestCase("{0", 1)]
+ [TestCase("{null", 1)]
+ [TestCase("{false", 1)]
+ [TestCase("{true", 1)]
+ [TestCase("}", 0)]
+ [TestCase("]", 0)]
+ [TestCase(",", 0)]
+ [TestCase("'foo' 'bar'", 1)]
+ [TestCase(":", 0)]
+ [TestCase("'foo", 0)] // Incomplete string
+ [TestCase("{ 'foo' }", 2)]
+ [TestCase("{ x:1", 1)] // Property names must be quoted
+ [TestCase("{]", 1)]
+ [TestCase("[}", 1)]
+ [TestCase("[1,", 2)]
+ [TestCase("{'x':0]", 3)]
+ [TestCase("{ 'foo': }", 2)]
+ [TestCase("{ 'foo':'bar', }", 3)]
+ public void InvalidStructure(string json, int expectedValidTokens)
+ {
+ // Note: we don't test that the earlier tokens are exactly as expected,
+ // partly because that's hard to parameterize.
+ var reader = new StringReader(json.Replace('\'', '"'));
+ var tokenizer = JsonTokenizer.FromTextReader(reader);
+ for (int i = 0; i < expectedValidTokens; i++)
+ {
+ Assert.IsNotNull(tokenizer.Next());
+ }
+ Assert.Throws<InvalidJsonException>(() => tokenizer.Next());
+ }
+
+ [Test]
+ public void ArrayMixedType()
+ {
+ AssertTokens("[1, 'foo', null, false, true, [2], {'x':'y' }]",
+ JsonToken.StartArray,
+ JsonToken.Value(1),
+ JsonToken.Value("foo"),
+ JsonToken.Null,
+ JsonToken.False,
+ JsonToken.True,
+ JsonToken.StartArray,
+ JsonToken.Value(2),
+ JsonToken.EndArray,
+ JsonToken.StartObject,
+ JsonToken.Name("x"),
+ JsonToken.Value("y"),
+ JsonToken.EndObject,
+ JsonToken.EndArray);
+ }
+
+ [Test]
+ public void ObjectMixedType()
+ {
+ AssertTokens(@"{'a': 1, 'b': 'bar', 'c': null, 'd': false, 'e': true,
+ 'f': [2], 'g': {'x':'y' }}",
+ JsonToken.StartObject,
+ JsonToken.Name("a"),
+ JsonToken.Value(1),
+ JsonToken.Name("b"),
+ JsonToken.Value("bar"),
+ JsonToken.Name("c"),
+ JsonToken.Null,
+ JsonToken.Name("d"),
+ JsonToken.False,
+ JsonToken.Name("e"),
+ JsonToken.True,
+ JsonToken.Name("f"),
+ JsonToken.StartArray,
+ JsonToken.Value(2),
+ JsonToken.EndArray,
+ JsonToken.Name("g"),
+ JsonToken.StartObject,
+ JsonToken.Name("x"),
+ JsonToken.Value("y"),
+ JsonToken.EndObject,
+ JsonToken.EndObject);
+ }
+
+ [Test]
+ public void NextAfterEndDocumentThrows()
+ {
+ var tokenizer = JsonTokenizer.FromTextReader(new StringReader("null"));
+ Assert.AreEqual(JsonToken.Null, tokenizer.Next());
+ Assert.AreEqual(JsonToken.EndDocument, tokenizer.Next());
+ Assert.Throws<InvalidOperationException>(() => tokenizer.Next());
+ }
+
+ [Test]
+ public void CanPushBackEndDocument()
+ {
+ var tokenizer = JsonTokenizer.FromTextReader(new StringReader("null"));
+ Assert.AreEqual(JsonToken.Null, tokenizer.Next());
+ Assert.AreEqual(JsonToken.EndDocument, tokenizer.Next());
+ tokenizer.PushBack(JsonToken.EndDocument);
+ Assert.AreEqual(JsonToken.EndDocument, tokenizer.Next());
+ Assert.Throws<InvalidOperationException>(() => tokenizer.Next());
+ }
+
+ /// <summary>
+ /// Asserts that the specified JSON is tokenized into the given sequence of tokens.
+ /// All apostrophes are first converted to double quotes, allowing any tests
+ /// that don't need to check actual apostrophe handling to use apostrophes in the JSON, avoiding
+ /// messy string literal escaping. The "end document" token is not specified in the list of
+ /// expected tokens, but is implicit.
+ /// </summary>
+ private static void AssertTokens(string json, params JsonToken[] expectedTokens)
+ {
+ AssertTokensNoReplacement(json.Replace('\'', '"'), expectedTokens);
+ }
+
+ /// <summary>
+ /// Asserts that the specified JSON is tokenized into the given sequence of tokens.
+ /// Unlike <see cref="AssertTokens(string, JsonToken[])"/>, this does not perform any character
+ /// replacement on the specified JSON, and should be used when the text contains apostrophes which
+ /// are expected to be used *as* apostrophes. The "end document" token is not specified in the list of
+ /// expected tokens, but is implicit.
+ /// </summary>
+ private static void AssertTokensNoReplacement(string json, params JsonToken[] expectedTokens)
+ {
+ var reader = new StringReader(json);
+ var tokenizer = JsonTokenizer.FromTextReader(reader);
+ for (int i = 0; i < expectedTokens.Length; i++)
+ {
+ var actualToken = tokenizer.Next();
+ if (actualToken == JsonToken.EndDocument)
+ {
+ Assert.Fail("Expected {0} but reached end of token stream", expectedTokens[i]);
+ }
+ Assert.AreEqual(expectedTokens[i], actualToken);
+ }
+ var finalToken = tokenizer.Next();
+ if (finalToken != JsonToken.EndDocument)
+ {
+ Assert.Fail("Expected token stream to be exhausted; received {0}", finalToken);
+ }
+ }
+
+ private static void AssertThrowsAfter(string json, params JsonToken[] expectedTokens)
+ {
+ var reader = new StringReader(json);
+ var tokenizer = JsonTokenizer.FromTextReader(reader);
+ for (int i = 0; i < expectedTokens.Length; i++)
+ {
+ var actualToken = tokenizer.Next();
+ if (actualToken == JsonToken.EndDocument)
+ {
+ Assert.Fail("Expected {0} but reached end of document", expectedTokens[i]);
+ }
+ Assert.AreEqual(expectedTokens[i], actualToken);
+ }
+ Assert.Throws<InvalidJsonException>(() => tokenizer.Next());
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs
new file mode 100644
index 00000000..52d5a676
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs
@@ -0,0 +1,259 @@
+#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.Linq;
+using Google.Protobuf.TestProtos;
+using NUnit.Framework;
+using UnitTest.Issues.TestProtos;
+
+namespace Google.Protobuf.Reflection
+{
+ /// <summary>
+ /// Tests for descriptors. (Not in its own namespace or broken up into individual classes as the
+ /// size doesn't warrant it. On the other hand, this makes me feel a bit dirty...)
+ /// </summary>
+ public class DescriptorsTest
+ {
+ [Test]
+ public void FileDescriptor()
+ {
+ FileDescriptor file = UnittestProto3Reflection.Descriptor;
+
+ Assert.AreEqual("google/protobuf/unittest_proto3.proto", file.Name);
+ Assert.AreEqual("protobuf_unittest", file.Package);
+
+ Assert.AreEqual("UnittestProto", file.Proto.Options.JavaOuterClassname);
+ Assert.AreEqual("google/protobuf/unittest_proto3.proto", file.Proto.Name);
+
+ // unittest.proto doesn't have any public imports, but unittest_import.proto does.
+ Assert.AreEqual(0, file.PublicDependencies.Count);
+ Assert.AreEqual(1, UnittestImportProto3Reflection.Descriptor.PublicDependencies.Count);
+ Assert.AreEqual(UnittestImportPublicProto3Reflection.Descriptor, UnittestImportProto3Reflection.Descriptor.PublicDependencies[0]);
+
+ Assert.AreEqual(1, file.Dependencies.Count);
+ Assert.AreEqual(UnittestImportProto3Reflection.Descriptor, file.Dependencies[0]);
+
+ MessageDescriptor messageType = TestAllTypes.Descriptor;
+ Assert.AreSame(typeof(TestAllTypes), messageType.ClrType);
+ Assert.AreSame(TestAllTypes.Parser, messageType.Parser);
+ Assert.AreEqual(messageType, file.MessageTypes[0]);
+ Assert.AreEqual(messageType, file.FindTypeByName<MessageDescriptor>("TestAllTypes"));
+ Assert.Null(file.FindTypeByName<MessageDescriptor>("NoSuchType"));
+ Assert.Null(file.FindTypeByName<MessageDescriptor>("protobuf_unittest.TestAllTypes"));
+ for (int i = 0; i < file.MessageTypes.Count; i++)
+ {
+ Assert.AreEqual(i, file.MessageTypes[i].Index);
+ }
+
+ Assert.AreEqual(file.EnumTypes[0], file.FindTypeByName<EnumDescriptor>("ForeignEnum"));
+ Assert.Null(file.FindTypeByName<EnumDescriptor>("NoSuchType"));
+ Assert.Null(file.FindTypeByName<EnumDescriptor>("protobuf_unittest.ForeignEnum"));
+ Assert.AreEqual(1, UnittestImportProto3Reflection.Descriptor.EnumTypes.Count);
+ Assert.AreEqual("ImportEnum", UnittestImportProto3Reflection.Descriptor.EnumTypes[0].Name);
+ for (int i = 0; i < file.EnumTypes.Count; i++)
+ {
+ Assert.AreEqual(i, file.EnumTypes[i].Index);
+ }
+
+ Assert.AreEqual(10, file.SerializedData[0]);
+ }
+
+ [Test]
+ public void MessageDescriptor()
+ {
+ MessageDescriptor messageType = TestAllTypes.Descriptor;
+ MessageDescriptor nestedType = TestAllTypes.Types.NestedMessage.Descriptor;
+
+ Assert.AreEqual("TestAllTypes", messageType.Name);
+ Assert.AreEqual("protobuf_unittest.TestAllTypes", messageType.FullName);
+ Assert.AreEqual(UnittestProto3Reflection.Descriptor, messageType.File);
+ Assert.IsNull(messageType.ContainingType);
+ Assert.IsNull(messageType.Proto.Options);
+
+ Assert.AreEqual("TestAllTypes", messageType.Name);
+
+ Assert.AreEqual("NestedMessage", nestedType.Name);
+ Assert.AreEqual("protobuf_unittest.TestAllTypes.NestedMessage", nestedType.FullName);
+ Assert.AreEqual(UnittestProto3Reflection.Descriptor, nestedType.File);
+ Assert.AreEqual(messageType, nestedType.ContainingType);
+
+ FieldDescriptor field = messageType.Fields.InDeclarationOrder()[0];
+ Assert.AreEqual("single_int32", field.Name);
+ Assert.AreEqual(field, messageType.FindDescriptor<FieldDescriptor>("single_int32"));
+ Assert.Null(messageType.FindDescriptor<FieldDescriptor>("no_such_field"));
+ Assert.AreEqual(field, messageType.FindFieldByNumber(1));
+ Assert.Null(messageType.FindFieldByNumber(571283));
+ var fieldsInDeclarationOrder = messageType.Fields.InDeclarationOrder();
+ for (int i = 0; i < fieldsInDeclarationOrder.Count; i++)
+ {
+ Assert.AreEqual(i, fieldsInDeclarationOrder[i].Index);
+ }
+
+ Assert.AreEqual(nestedType, messageType.NestedTypes[0]);
+ Assert.AreEqual(nestedType, messageType.FindDescriptor<MessageDescriptor>("NestedMessage"));
+ Assert.Null(messageType.FindDescriptor<MessageDescriptor>("NoSuchType"));
+ for (int i = 0; i < messageType.NestedTypes.Count; i++)
+ {
+ Assert.AreEqual(i, messageType.NestedTypes[i].Index);
+ }
+
+ Assert.AreEqual(messageType.EnumTypes[0], messageType.FindDescriptor<EnumDescriptor>("NestedEnum"));
+ Assert.Null(messageType.FindDescriptor<EnumDescriptor>("NoSuchType"));
+ for (int i = 0; i < messageType.EnumTypes.Count; i++)
+ {
+ Assert.AreEqual(i, messageType.EnumTypes[i].Index);
+ }
+ }
+
+ [Test]
+ public void FieldDescriptor()
+ {
+ MessageDescriptor messageType = TestAllTypes.Descriptor;
+ FieldDescriptor primitiveField = messageType.FindDescriptor<FieldDescriptor>("single_int32");
+ FieldDescriptor enumField = messageType.FindDescriptor<FieldDescriptor>("single_nested_enum");
+ FieldDescriptor messageField = messageType.FindDescriptor<FieldDescriptor>("single_foreign_message");
+
+ Assert.AreEqual("single_int32", primitiveField.Name);
+ Assert.AreEqual("protobuf_unittest.TestAllTypes.single_int32",
+ primitiveField.FullName);
+ Assert.AreEqual(1, primitiveField.FieldNumber);
+ Assert.AreEqual(messageType, primitiveField.ContainingType);
+ Assert.AreEqual(UnittestProto3Reflection.Descriptor, primitiveField.File);
+ Assert.AreEqual(FieldType.Int32, primitiveField.FieldType);
+ Assert.IsNull(primitiveField.Proto.Options);
+
+ Assert.AreEqual("single_nested_enum", enumField.Name);
+ Assert.AreEqual(FieldType.Enum, enumField.FieldType);
+ // Assert.AreEqual(TestAllTypes.Types.NestedEnum.DescriptorProtoFile, enumField.EnumType);
+
+ Assert.AreEqual("single_foreign_message", messageField.Name);
+ Assert.AreEqual(FieldType.Message, messageField.FieldType);
+ Assert.AreEqual(ForeignMessage.Descriptor, messageField.MessageType);
+ }
+
+ [Test]
+ public void FieldDescriptorLabel()
+ {
+ FieldDescriptor singleField =
+ TestAllTypes.Descriptor.FindDescriptor<FieldDescriptor>("single_int32");
+ FieldDescriptor repeatedField =
+ TestAllTypes.Descriptor.FindDescriptor<FieldDescriptor>("repeated_int32");
+
+ Assert.IsFalse(singleField.IsRepeated);
+ Assert.IsTrue(repeatedField.IsRepeated);
+ }
+
+ [Test]
+ public void EnumDescriptor()
+ {
+ // Note: this test is a bit different to the Java version because there's no static way of getting to the descriptor
+ EnumDescriptor enumType = UnittestProto3Reflection.Descriptor.FindTypeByName<EnumDescriptor>("ForeignEnum");
+ EnumDescriptor nestedType = TestAllTypes.Descriptor.FindDescriptor<EnumDescriptor>("NestedEnum");
+
+ Assert.AreEqual("ForeignEnum", enumType.Name);
+ Assert.AreEqual("protobuf_unittest.ForeignEnum", enumType.FullName);
+ Assert.AreEqual(UnittestProto3Reflection.Descriptor, enumType.File);
+ Assert.Null(enumType.ContainingType);
+ Assert.Null(enumType.Proto.Options);
+
+ Assert.AreEqual("NestedEnum", nestedType.Name);
+ Assert.AreEqual("protobuf_unittest.TestAllTypes.NestedEnum",
+ nestedType.FullName);
+ Assert.AreEqual(UnittestProto3Reflection.Descriptor, nestedType.File);
+ Assert.AreEqual(TestAllTypes.Descriptor, nestedType.ContainingType);
+
+ EnumValueDescriptor value = enumType.FindValueByName("FOREIGN_FOO");
+ Assert.AreEqual(value, enumType.Values[1]);
+ Assert.AreEqual("FOREIGN_FOO", value.Name);
+ Assert.AreEqual(4, value.Number);
+ Assert.AreEqual((int) ForeignEnum.ForeignFoo, value.Number);
+ Assert.AreEqual(value, enumType.FindValueByNumber(4));
+ Assert.Null(enumType.FindValueByName("NO_SUCH_VALUE"));
+ for (int i = 0; i < enumType.Values.Count; i++)
+ {
+ Assert.AreEqual(i, enumType.Values[i].Index);
+ }
+ }
+
+ [Test]
+ public void OneofDescriptor()
+ {
+ OneofDescriptor descriptor = TestAllTypes.Descriptor.FindDescriptor<OneofDescriptor>("oneof_field");
+ Assert.AreEqual("oneof_field", descriptor.Name);
+ Assert.AreEqual("protobuf_unittest.TestAllTypes.oneof_field", descriptor.FullName);
+
+ var expectedFields = new[] {
+ TestAllTypes.OneofBytesFieldNumber,
+ TestAllTypes.OneofNestedMessageFieldNumber,
+ TestAllTypes.OneofStringFieldNumber,
+ TestAllTypes.OneofUint32FieldNumber }
+ .Select(fieldNumber => TestAllTypes.Descriptor.FindFieldByNumber(fieldNumber))
+ .ToList();
+ foreach (var field in expectedFields)
+ {
+ Assert.AreSame(descriptor, field.ContainingOneof);
+ }
+
+ CollectionAssert.AreEquivalent(expectedFields, descriptor.Fields);
+ }
+
+ [Test]
+ public void MapEntryMessageDescriptor()
+ {
+ var descriptor = MapWellKnownTypes.Descriptor.NestedTypes[0];
+ Assert.IsNull(descriptor.Parser);
+ Assert.IsNull(descriptor.ClrType);
+ Assert.IsNull(descriptor.Fields[1].Accessor);
+ }
+
+ // From TestFieldOrdering:
+ // string my_string = 11;
+ // int64 my_int = 1;
+ // float my_float = 101;
+ // NestedMessage single_nested_message = 200;
+ [Test]
+ public void FieldListOrderings()
+ {
+ var fields = TestFieldOrderings.Descriptor.Fields;
+ Assert.AreEqual(new[] { 11, 1, 101, 200 }, fields.InDeclarationOrder().Select(x => x.FieldNumber));
+ Assert.AreEqual(new[] { 1, 11, 101, 200 }, fields.InFieldNumberOrder().Select(x => x.FieldNumber));
+ }
+
+
+ [Test]
+ public void DescriptorProtoFileDescriptor()
+ {
+ var descriptor = Google.Protobuf.Reflection.FileDescriptor.DescriptorProtoFileDescriptor;
+ Assert.AreEqual("google/protobuf/descriptor.proto", descriptor.Name);
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs
new file mode 100644
index 00000000..a488af30
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs
@@ -0,0 +1,218 @@
+#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 Google.Protobuf.TestProtos;
+using NUnit.Framework;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Google.Protobuf.Reflection
+{
+ public class FieldAccessTest
+ {
+ [Test]
+ public void GetValue()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var fields = TestAllTypes.Descriptor.Fields;
+ Assert.AreEqual(message.SingleBool, fields[TestAllTypes.SingleBoolFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleBytes, fields[TestAllTypes.SingleBytesFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleDouble, fields[TestAllTypes.SingleDoubleFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleFixed32, fields[TestAllTypes.SingleFixed32FieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleFixed64, fields[TestAllTypes.SingleFixed64FieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleFloat, fields[TestAllTypes.SingleFloatFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleForeignEnum, fields[TestAllTypes.SingleForeignEnumFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleForeignMessage, fields[TestAllTypes.SingleForeignMessageFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleImportEnum, fields[TestAllTypes.SingleImportEnumFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleImportMessage, fields[TestAllTypes.SingleImportMessageFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleInt32, fields[TestAllTypes.SingleInt32FieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleInt64, fields[TestAllTypes.SingleInt64FieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleNestedEnum, fields[TestAllTypes.SingleNestedEnumFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleNestedMessage, fields[TestAllTypes.SingleNestedMessageFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SinglePublicImportMessage, fields[TestAllTypes.SinglePublicImportMessageFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleSint32, fields[TestAllTypes.SingleSint32FieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleSint64, fields[TestAllTypes.SingleSint64FieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleString, fields[TestAllTypes.SingleStringFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleSfixed32, fields[TestAllTypes.SingleSfixed32FieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleSfixed64, fields[TestAllTypes.SingleSfixed64FieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleUint32, fields[TestAllTypes.SingleUint32FieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.SingleUint64, fields[TestAllTypes.SingleUint64FieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.OneofBytes, fields[TestAllTypes.OneofBytesFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.OneofString, fields[TestAllTypes.OneofStringFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.OneofNestedMessage, fields[TestAllTypes.OneofNestedMessageFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(message.OneofUint32, fields[TestAllTypes.OneofUint32FieldNumber].Accessor.GetValue(message));
+
+ // Just one example for repeated fields - they're all just returning the list
+ var list = (IList) fields[TestAllTypes.RepeatedInt32FieldNumber].Accessor.GetValue(message);
+ Assert.AreEqual(message.RepeatedInt32, list);
+ Assert.AreEqual(message.RepeatedInt32[0], list[0]); // Just in case there was any doubt...
+
+ // Just a single map field, for the same reason
+ var mapMessage = new TestMap { MapStringString = { { "key1", "value1" }, { "key2", "value2" } } };
+ fields = TestMap.Descriptor.Fields;
+ var dictionary = (IDictionary) fields[TestMap.MapStringStringFieldNumber].Accessor.GetValue(mapMessage);
+ Assert.AreEqual(mapMessage.MapStringString, dictionary);
+ Assert.AreEqual("value1", dictionary["key1"]);
+ }
+
+ [Test]
+ public void Clear()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var fields = TestAllTypes.Descriptor.Fields;
+ fields[TestAllTypes.SingleBoolFieldNumber].Accessor.Clear(message);
+ fields[TestAllTypes.SingleInt32FieldNumber].Accessor.Clear(message);
+ fields[TestAllTypes.SingleStringFieldNumber].Accessor.Clear(message);
+ fields[TestAllTypes.SingleBytesFieldNumber].Accessor.Clear(message);
+ fields[TestAllTypes.SingleForeignEnumFieldNumber].Accessor.Clear(message);
+ fields[TestAllTypes.SingleForeignMessageFieldNumber].Accessor.Clear(message);
+ fields[TestAllTypes.RepeatedDoubleFieldNumber].Accessor.Clear(message);
+
+ var expected = new TestAllTypes(SampleMessages.CreateFullTestAllTypes())
+ {
+ SingleBool = false,
+ SingleInt32 = 0,
+ SingleString = "",
+ SingleBytes = ByteString.Empty,
+ SingleForeignEnum = 0,
+ SingleForeignMessage = null,
+ };
+ expected.RepeatedDouble.Clear();
+
+ Assert.AreEqual(expected, message);
+
+ // Separately, maps.
+ var mapMessage = new TestMap { MapStringString = { { "key1", "value1" }, { "key2", "value2" } } };
+ fields = TestMap.Descriptor.Fields;
+ fields[TestMap.MapStringStringFieldNumber].Accessor.Clear(mapMessage);
+ Assert.AreEqual(0, mapMessage.MapStringString.Count);
+ }
+
+ [Test]
+ public void SetValue_SingleFields()
+ {
+ // Just a sample (primitives, messages, enums, strings, byte strings)
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var fields = TestAllTypes.Descriptor.Fields;
+ fields[TestAllTypes.SingleBoolFieldNumber].Accessor.SetValue(message, false);
+ fields[TestAllTypes.SingleInt32FieldNumber].Accessor.SetValue(message, 500);
+ fields[TestAllTypes.SingleStringFieldNumber].Accessor.SetValue(message, "It's a string");
+ fields[TestAllTypes.SingleBytesFieldNumber].Accessor.SetValue(message, ByteString.CopyFrom(99, 98, 97));
+ fields[TestAllTypes.SingleForeignEnumFieldNumber].Accessor.SetValue(message, ForeignEnum.ForeignFoo);
+ fields[TestAllTypes.SingleForeignMessageFieldNumber].Accessor.SetValue(message, new ForeignMessage { C = 12345 });
+ fields[TestAllTypes.SingleDoubleFieldNumber].Accessor.SetValue(message, 20150701.5);
+
+ var expected = new TestAllTypes(SampleMessages.CreateFullTestAllTypes())
+ {
+ SingleBool = false,
+ SingleInt32 = 500,
+ SingleString = "It's a string",
+ SingleBytes = ByteString.CopyFrom(99, 98, 97),
+ SingleForeignEnum = ForeignEnum.ForeignFoo,
+ SingleForeignMessage = new ForeignMessage { C = 12345 },
+ SingleDouble = 20150701.5
+ };
+
+ Assert.AreEqual(expected, message);
+ }
+
+ [Test]
+ public void SetValue_SingleFields_WrongType()
+ {
+ IMessage message = SampleMessages.CreateFullTestAllTypes();
+ var fields = message.Descriptor.Fields;
+ Assert.Throws<InvalidCastException>(() => fields[TestAllTypes.SingleBoolFieldNumber].Accessor.SetValue(message, "This isn't a bool"));
+ }
+
+ [Test]
+ public void SetValue_MapFields()
+ {
+ IMessage message = new TestMap();
+ var fields = message.Descriptor.Fields;
+ Assert.Throws<InvalidOperationException>(() => fields[TestMap.MapStringStringFieldNumber].Accessor.SetValue(message, new Dictionary<string, string>()));
+ }
+
+ [Test]
+ public void SetValue_RepeatedFields()
+ {
+ IMessage message = SampleMessages.CreateFullTestAllTypes();
+ var fields = message.Descriptor.Fields;
+ Assert.Throws<InvalidOperationException>(() => fields[TestAllTypes.RepeatedDoubleFieldNumber].Accessor.SetValue(message, new double[10]));
+ }
+
+ [Test]
+ public void GetValue_IncorrectType()
+ {
+ IMessage message = SampleMessages.CreateFullTestAllTypes();
+ var fields = message.Descriptor.Fields;
+ Assert.Throws<InvalidCastException>(() => fields[TestAllTypes.SingleBoolFieldNumber].Accessor.GetValue(new TestMap()));
+ }
+
+ [Test]
+ public void Oneof()
+ {
+ var message = new TestAllTypes();
+ var descriptor = TestAllTypes.Descriptor;
+ Assert.AreEqual(1, descriptor.Oneofs.Count);
+ var oneof = descriptor.Oneofs[0];
+ Assert.AreEqual("oneof_field", oneof.Name);
+ Assert.IsNull(oneof.Accessor.GetCaseFieldDescriptor(message));
+
+ message.OneofString = "foo";
+ Assert.AreSame(descriptor.Fields[TestAllTypes.OneofStringFieldNumber], oneof.Accessor.GetCaseFieldDescriptor(message));
+
+ message.OneofUint32 = 10;
+ Assert.AreSame(descriptor.Fields[TestAllTypes.OneofUint32FieldNumber], oneof.Accessor.GetCaseFieldDescriptor(message));
+
+ oneof.Accessor.Clear(message);
+ Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.None, message.OneofFieldCase);
+ }
+
+ [Test]
+ public void FieldDescriptor_ByName()
+ {
+ var descriptor = TestAllTypes.Descriptor;
+ Assert.AreSame(
+ descriptor.Fields[TestAllTypes.SingleBoolFieldNumber],
+ descriptor.Fields["single_bool"]);
+ }
+
+ [Test]
+ public void FieldDescriptor_NotFound()
+ {
+ var descriptor = TestAllTypes.Descriptor;
+ Assert.Throws<KeyNotFoundException>(() => descriptor.Fields[999999].ToString());
+ Assert.Throws<KeyNotFoundException>(() => descriptor.Fields["not found"].ToString());
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs
new file mode 100644
index 00000000..5be7ca23
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs
@@ -0,0 +1,94 @@
+#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 Google.Protobuf.TestProtos;
+using Google.Protobuf.WellKnownTypes;
+using NUnit.Framework;
+
+namespace Google.Protobuf.Reflection
+{
+ public class TypeRegistryTest
+ {
+ // Most of our tests use messages. Simple test that we really can use files...
+ [Test]
+ public void CreateWithFileDescriptor()
+ {
+ var registry = TypeRegistry.FromFiles(DurationReflection.Descriptor, StructReflection.Descriptor);
+ AssertDescriptorPresent(registry, Duration.Descriptor);
+ AssertDescriptorPresent(registry, ListValue.Descriptor);
+ AssertDescriptorAbsent(registry, Timestamp.Descriptor);
+ }
+
+ [Test]
+ public void TypesFromSameFile()
+ {
+ // Just for kicks, let's start with a nested type
+ var registry = TypeRegistry.FromMessages(TestAllTypes.Types.NestedMessage.Descriptor);
+ // Top-level...
+ AssertDescriptorPresent(registry, TestFieldOrderings.Descriptor);
+ // ... and nested (not the same as the original NestedMessage!)
+ AssertDescriptorPresent(registry, TestFieldOrderings.Types.NestedMessage.Descriptor);
+ }
+
+ [Test]
+ public void DependenciesAreIncluded()
+ {
+ var registry = TypeRegistry.FromMessages(TestAllTypes.Descriptor);
+ // Direct dependencies
+ AssertDescriptorPresent(registry, ImportMessage.Descriptor);
+ // Public dependencies
+ AssertDescriptorPresent(registry, PublicImportMessage.Descriptor);
+ }
+
+ [Test]
+ public void DuplicateFiles()
+ {
+ // Duplicates via dependencies and simply via repetition
+ var registry = TypeRegistry.FromFiles(
+ UnittestProto3Reflection.Descriptor, UnittestImportProto3Reflection.Descriptor,
+ TimestampReflection.Descriptor, TimestampReflection.Descriptor);
+ AssertDescriptorPresent(registry, TestAllTypes.Descriptor);
+ AssertDescriptorPresent(registry, ImportMessage.Descriptor);
+ AssertDescriptorPresent(registry, Timestamp.Descriptor);
+ }
+
+ private static void AssertDescriptorPresent(TypeRegistry registry, MessageDescriptor descriptor)
+ {
+ Assert.AreSame(descriptor, registry.Find(descriptor.FullName));
+ }
+
+ private static void AssertDescriptorAbsent(TypeRegistry registry, MessageDescriptor descriptor)
+ {
+ Assert.IsNull(registry.Find(descriptor.FullName));
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/SampleEnum.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/SampleEnum.cs
new file mode 100644
index 00000000..77447afa
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/SampleEnum.cs
@@ -0,0 +1,42 @@
+#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
+
+namespace Google.Protobuf
+{
+ // Just a sample enum with positive and negative values to be used in tests.
+ internal enum SampleEnum
+ {
+ NegativeValue = -2,
+ None = 0,
+ PositiveValue = 3
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/SampleMessages.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/SampleMessages.cs
new file mode 100644
index 00000000..ffa4e2a7
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/SampleMessages.cs
@@ -0,0 +1,99 @@
+#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 System;
+using Google.Protobuf.TestProtos;
+
+namespace Google.Protobuf
+{
+ /// <summary>
+ /// Helper methods to create sample instances of types generated from unit test messages.
+ /// </summary>
+ public class SampleMessages
+ {
+ /// <summary>
+ /// Creates a new sample TestAllTypes message with all fields populated.
+ /// The "oneof" field is populated with the string property (OneofString).
+ /// </summary>
+ public static TestAllTypes CreateFullTestAllTypes()
+ {
+ return new TestAllTypes
+ {
+ SingleBool = true,
+ SingleBytes = ByteString.CopyFrom(1, 2, 3, 4),
+ SingleDouble = 23.5,
+ SingleFixed32 = 23,
+ SingleFixed64 = 1234567890123,
+ SingleFloat = 12.25f,
+ SingleForeignEnum = ForeignEnum.ForeignBar,
+ SingleForeignMessage = new ForeignMessage { C = 10 },
+ SingleImportEnum = ImportEnum.ImportBaz,
+ SingleImportMessage = new ImportMessage { D = 20 },
+ SingleInt32 = 100,
+ SingleInt64 = 3210987654321,
+ SingleNestedEnum = TestAllTypes.Types.NestedEnum.Foo,
+ SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 35 },
+ SinglePublicImportMessage = new PublicImportMessage { E = 54 },
+ SingleSfixed32 = -123,
+ SingleSfixed64 = -12345678901234,
+ SingleSint32 = -456,
+ SingleSint64 = -12345678901235,
+ SingleString = "test",
+ SingleUint32 = UInt32.MaxValue,
+ SingleUint64 = UInt64.MaxValue,
+ RepeatedBool = { true, false },
+ RepeatedBytes = { ByteString.CopyFrom(1, 2, 3, 4), ByteString.CopyFrom(5, 6), ByteString.CopyFrom(new byte[1000]) },
+ RepeatedDouble = { -12.25, 23.5 },
+ RepeatedFixed32 = { UInt32.MaxValue, 23 },
+ RepeatedFixed64 = { UInt64.MaxValue, 1234567890123 },
+ RepeatedFloat = { 100f, 12.25f },
+ RepeatedForeignEnum = { ForeignEnum.ForeignFoo, ForeignEnum.ForeignBar },
+ RepeatedForeignMessage = { new ForeignMessage(), new ForeignMessage { C = 10 } },
+ RepeatedImportEnum = { ImportEnum.ImportBaz, ImportEnum.Unspecified },
+ RepeatedImportMessage = { new ImportMessage { D = 20 }, new ImportMessage { D = 25 } },
+ RepeatedInt32 = { 100, 200 },
+ RepeatedInt64 = { 3210987654321, Int64.MaxValue },
+ RepeatedNestedEnum = { TestAllTypes.Types.NestedEnum.Foo, TestAllTypes.Types.NestedEnum.Neg },
+ RepeatedNestedMessage = { new TestAllTypes.Types.NestedMessage { Bb = 35 }, new TestAllTypes.Types.NestedMessage { Bb = 10 } },
+ RepeatedPublicImportMessage = { new PublicImportMessage { E = 54 }, new PublicImportMessage { E = -1 } },
+ RepeatedSfixed32 = { -123, 123 },
+ RepeatedSfixed64 = { -12345678901234, 12345678901234 },
+ RepeatedSint32 = { -456, 100 },
+ RepeatedSint64 = { -12345678901235, 123 },
+ RepeatedString = { "foo", "bar" },
+ RepeatedUint32 = { UInt32.MaxValue, UInt32.MinValue },
+ RepeatedUint64 = { UInt64.MaxValue, UInt32.MinValue },
+ OneofString = "Oneof string"
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/TestCornerCases.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/TestCornerCases.cs
new file mode 100644
index 00000000..248f5fa9
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/TestCornerCases.cs
@@ -0,0 +1,62 @@
+#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 UnitTest.Issues.TestProtos;
+using NUnit.Framework;
+
+namespace Google.Protobuf
+{
+ public class TestCornerCases
+ {
+ [Test]
+ public void TestRoundTripNegativeEnums()
+ {
+ NegativeEnumMessage msg = new NegativeEnumMessage
+ {
+ Value = NegativeEnum.MinusOne,
+ Values = { NegativeEnum.Zero, NegativeEnum.MinusOne, NegativeEnum.FiveBelow },
+ PackedValues = { NegativeEnum.Zero, NegativeEnum.MinusOne, NegativeEnum.FiveBelow }
+ };
+
+ Assert.AreEqual(58, msg.CalculateSize());
+
+ byte[] bytes = new byte[58];
+ CodedOutputStream output = new CodedOutputStream(bytes);
+
+ msg.WriteTo(output);
+ Assert.AreEqual(0, output.SpaceLeft);
+
+ NegativeEnumMessage copy = NegativeEnumMessage.Parser.ParseFrom(bytes);
+ Assert.AreEqual(msg, copy);
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/TestProtos/ForeignMessagePartial.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/TestProtos/ForeignMessagePartial.cs
new file mode 100644
index 00000000..5663a699
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/TestProtos/ForeignMessagePartial.cs
@@ -0,0 +1,45 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2016 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
+
+namespace Google.Protobuf.TestProtos
+{
+ /// <summary>
+ /// A message with custom diagnostics (to test that they work).
+ /// </summary>
+ public partial class ForeignMessage : ICustomDiagnosticMessage
+ {
+ public string ToDiagnosticString()
+ {
+ return $"{{ \"c\": {C}, \"@cInHex\": \"{C:x}\" }}";
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs
new file mode 100644
index 00000000..4aecc998
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs
@@ -0,0 +1,116 @@
+#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 Google.Protobuf.TestProtos;
+using NUnit.Framework;
+
+namespace Google.Protobuf.WellKnownTypes
+{
+ public class AnyTest
+ {
+ [Test]
+ public void Pack()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var any = Any.Pack(message);
+ Assert.AreEqual("type.googleapis.com/protobuf_unittest.TestAllTypes", any.TypeUrl);
+ Assert.AreEqual(message.CalculateSize(), any.Value.Length);
+ }
+
+ [Test]
+ public void Pack_WithCustomPrefix()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var any = Any.Pack(message, "foo.bar/baz");
+ Assert.AreEqual("foo.bar/baz/protobuf_unittest.TestAllTypes", any.TypeUrl);
+ Assert.AreEqual(message.CalculateSize(), any.Value.Length);
+ }
+
+ [Test]
+ public void Pack_WithCustomPrefixTrailingSlash()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var any = Any.Pack(message, "foo.bar/baz/");
+ Assert.AreEqual("foo.bar/baz/protobuf_unittest.TestAllTypes", any.TypeUrl);
+ Assert.AreEqual(message.CalculateSize(), any.Value.Length);
+ }
+
+ [Test]
+ public void Unpack_WrongType()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var any = Any.Pack(message);
+ Assert.Throws<InvalidProtocolBufferException>(() => any.Unpack<TestOneof>());
+ }
+
+ [Test]
+ public void Unpack_Success()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var any = Any.Pack(message);
+ var unpacked = any.Unpack<TestAllTypes>();
+ Assert.AreEqual(message, unpacked);
+ }
+
+ [Test]
+ public void Unpack_CustomPrefix_Success()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var any = Any.Pack(message, "foo.bar/baz");
+ var unpacked = any.Unpack<TestAllTypes>();
+ Assert.AreEqual(message, unpacked);
+ }
+
+ [Test]
+ public void ToString_WithValues()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var any = Any.Pack(message);
+ var text = any.ToString();
+ Assert.That(text, Does.Contain("\"@value\": \"" + message.ToByteString().ToBase64() + "\""));
+ }
+
+ [Test]
+ public void ToString_Empty()
+ {
+ var any = new Any();
+ Assert.AreEqual("{ \"@type\": \"\", \"@value\": \"\" }", any.ToString());
+ }
+
+ [Test]
+ public void ToString_MessageContainingAny()
+ {
+ var message = new TestWellKnownTypes { AnyField = new Any() };
+ Assert.AreEqual("{ \"anyField\": { \"@type\": \"\", \"@value\": \"\" } }", message.ToString());
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/DurationTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/DurationTest.cs
new file mode 100644
index 00000000..141faf80
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/DurationTest.cs
@@ -0,0 +1,132 @@
+#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;
+
+namespace Google.Protobuf.WellKnownTypes
+{
+ public class DurationTest
+ {
+ [Test]
+ public void ToTimeSpan()
+ {
+ Assert.AreEqual(TimeSpan.FromSeconds(1), new Duration { Seconds = 1 }.ToTimeSpan());
+ Assert.AreEqual(TimeSpan.FromSeconds(-1), new Duration { Seconds = -1 }.ToTimeSpan());
+ Assert.AreEqual(TimeSpan.FromMilliseconds(1), new Duration { Nanos = 1000000 }.ToTimeSpan());
+ Assert.AreEqual(TimeSpan.FromMilliseconds(-1), new Duration { Nanos = -1000000 }.ToTimeSpan());
+ Assert.AreEqual(TimeSpan.FromTicks(1), new Duration { Nanos = 100 }.ToTimeSpan());
+ Assert.AreEqual(TimeSpan.FromTicks(-1), new Duration { Nanos = -100 }.ToTimeSpan());
+
+ // Rounding is towards 0
+ Assert.AreEqual(TimeSpan.FromTicks(2), new Duration { Nanos = 250 }.ToTimeSpan());
+ Assert.AreEqual(TimeSpan.FromTicks(-2), new Duration { Nanos = -250 }.ToTimeSpan());
+ }
+
+ [Test]
+ public void Addition()
+ {
+ Assert.AreEqual(new Duration { Seconds = 2, Nanos = 100000000 },
+ new Duration { Seconds = 1, Nanos = 600000000 } + new Duration { Nanos = 500000000 });
+ Assert.AreEqual(new Duration { Seconds = -2, Nanos = -100000000 },
+ new Duration { Seconds = -1, Nanos = -600000000 } + new Duration { Nanos = -500000000 });
+ Assert.AreEqual(new Duration { Seconds = 1, Nanos = 100000000 },
+ new Duration { Seconds = 1, Nanos = 600000000 } + new Duration { Nanos = -500000000 });
+
+ // Non-normalized durations, or non-normalized intermediate results
+ Assert.AreEqual(new Duration { Seconds = 1 },
+ new Duration { Seconds = 1, Nanos = -500000000 } + new Duration { Nanos = 500000000 });
+
+ Assert.AreEqual(new Duration { Nanos = -900000000 },
+ new Duration { Seconds = -1, Nanos = -100000000 } + new Duration { Nanos = 200000000 });
+ Assert.AreEqual(new Duration { Nanos = 900000000 },
+ new Duration { Seconds = 1, Nanos = 100000000 } + new Duration { Nanos = -200000000 });
+ }
+
+ [Test]
+ public void Subtraction()
+ {
+ Assert.AreEqual(new Duration { Seconds = 1, Nanos = 100000000 },
+ new Duration { Seconds = 1, Nanos = 600000000 } - new Duration { Nanos = 500000000 });
+ Assert.AreEqual(new Duration { Seconds = -1, Nanos = -100000000 },
+ new Duration { Seconds = -1, Nanos = -600000000 } - new Duration { Nanos = -500000000 });
+ Assert.AreEqual(new Duration { Seconds = 2, Nanos = 100000000 },
+ new Duration { Seconds = 1, Nanos = 600000000 } - new Duration { Nanos = -500000000 });
+
+ // Non-normalized durations
+ Assert.AreEqual(new Duration(),
+ new Duration { Seconds = 1, Nanos = -500000000 } - new Duration { Nanos = 500000000 });
+ Assert.AreEqual(new Duration { Seconds = 1 },
+ new Duration { Nanos = 2000000000 } - new Duration { Nanos = 1000000000 });
+ }
+
+ [Test]
+ public void FromTimeSpan()
+ {
+ Assert.AreEqual(new Duration { Seconds = 1 }, Duration.FromTimeSpan(TimeSpan.FromSeconds(1)));
+ Assert.AreEqual(new Duration { Nanos = Duration.NanosecondsPerTick }, Duration.FromTimeSpan(TimeSpan.FromTicks(1)));
+ }
+
+ [Test]
+ [TestCase(0, Duration.MaxNanoseconds + 1)]
+ [TestCase(0, Duration.MinNanoseconds - 1)]
+ [TestCase(Duration.MinSeconds - 1, 0)]
+ [TestCase(Duration.MaxSeconds + 1, 0)]
+ [TestCase(1, -1)]
+ [TestCase(-1, 1)]
+ public void ToTimeSpan_Invalid(long seconds, int nanoseconds)
+ {
+ var duration = new Duration { Seconds = seconds, Nanos = nanoseconds };
+ Assert.Throws<InvalidOperationException>(() => duration.ToTimeSpan());
+ }
+
+ [Test]
+ [TestCase(0, Duration.MaxNanoseconds)]
+ [TestCase(0, Duration.MinNanoseconds)]
+ [TestCase(Duration.MinSeconds, Duration.MinNanoseconds)]
+ [TestCase(Duration.MaxSeconds, Duration.MaxNanoseconds)]
+ public void ToTimeSpan_Valid(long seconds, int nanoseconds)
+ {
+ // Only testing that these values don't throw, unlike their similar tests in ToTimeSpan_Invalid
+ var duration = new Duration { Seconds = seconds, Nanos = nanoseconds };
+ duration.ToTimeSpan();
+ }
+
+ [Test]
+ public void ToString_NonNormalized()
+ {
+ // Just a single example should be sufficient...
+ var duration = new Duration { Seconds = 1, Nanos = -1 };
+ Assert.AreEqual("{ \"@warning\": \"Invalid Duration\", \"seconds\": \"1\", \"nanos\": -1 }", duration.ToString());
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/FieldMaskTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/FieldMaskTest.cs
new file mode 100644
index 00000000..1d9908b4
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/FieldMaskTest.cs
@@ -0,0 +1,62 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2016 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;
+
+namespace Google.Protobuf.WellKnownTypes
+{
+ public class FieldMaskTest
+ {
+ [Test]
+ [TestCase("foo__bar")]
+ [TestCase("foo_3_ar")]
+ [TestCase("fooBar")]
+ public void ToString_Invalid(string input)
+ {
+ var mask = new FieldMask { Paths = { input } };
+ var text = mask.ToString();
+ // More specific test below
+ Assert.That(text, Does.Contain("@warning"));
+ Assert.That(text, Does.Contain(input));
+ }
+
+ [Test]
+ public void ToString_Invalid_Precise()
+ {
+ var mask = new FieldMask { Paths = { "x", "foo__bar", @"x\y" } };
+ Assert.AreEqual(
+ "{ \"@warning\": \"Invalid FieldMask\", \"paths\": [ \"x\", \"foo__bar\", \"x\\\\y\" ] }",
+ mask.ToString());
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/TimestampTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/TimestampTest.cs
new file mode 100644
index 00000000..9ecd24c6
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/TimestampTest.cs
@@ -0,0 +1,115 @@
+#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;
+
+namespace Google.Protobuf.WellKnownTypes
+{
+ public class TimestampTest
+ {
+ [Test]
+ public void FromAndToDateTime()
+ {
+ DateTime utcMin = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
+ DateTime utcMax = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc);
+ AssertRoundtrip(new Timestamp { Seconds = -62135596800 }, utcMin);
+ AssertRoundtrip(new Timestamp { Seconds = 253402300799, Nanos = 999999900 }, utcMax);
+ AssertRoundtrip(new Timestamp(), new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc));
+ AssertRoundtrip(new Timestamp { Nanos = 1000000}, new DateTime(1970, 1, 1, 0, 0, 0, 1, DateTimeKind.Utc));
+ AssertRoundtrip(new Timestamp { Seconds = -1, Nanos = 999000000 }, new DateTime(1969, 12, 31, 23, 59, 59, 999, DateTimeKind.Utc));
+ AssertRoundtrip(new Timestamp { Seconds = 3600 }, new DateTime(1970, 1, 1, 1, 0, 0, DateTimeKind.Utc));
+ AssertRoundtrip(new Timestamp { Seconds = -3600 }, new DateTime(1969, 12, 31, 23, 0, 0, DateTimeKind.Utc));
+ }
+
+ [Test]
+ public void ToDateTimeTruncation()
+ {
+ var t1 = new Timestamp { Seconds = 1, Nanos = 1000000 + Duration.NanosecondsPerTick - 1 };
+ Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 1, DateTimeKind.Utc).AddMilliseconds(1), t1.ToDateTime());
+
+ var t2 = new Timestamp { Seconds = -1, Nanos = 1000000 + Duration.NanosecondsPerTick - 1 };
+ Assert.AreEqual(new DateTime(1969, 12, 31, 23, 59, 59).AddMilliseconds(1), t2.ToDateTime());
+ }
+
+ [Test]
+ [TestCase(Timestamp.UnixSecondsAtBclMinValue - 1, Timestamp.MaxNanos)]
+ [TestCase(Timestamp.UnixSecondsAtBclMaxValue + 1, 0)]
+ [TestCase(0, -1)]
+ [TestCase(0, Timestamp.MaxNanos + 1)]
+ public void ToDateTime_OutOfRange(long seconds, int nanoseconds)
+ {
+ var value = new Timestamp { Seconds = seconds, Nanos = nanoseconds };
+ Assert.Throws<InvalidOperationException>(() => value.ToDateTime());
+ }
+
+ // 1ns larger or smaller than the above values
+ [Test]
+ [TestCase(Timestamp.UnixSecondsAtBclMinValue, 0)]
+ [TestCase(Timestamp.UnixSecondsAtBclMaxValue, Timestamp.MaxNanos)]
+ [TestCase(0, 0)]
+ [TestCase(0, Timestamp.MaxNanos)]
+ public void ToDateTime_ValidBoundaries(long seconds, int nanoseconds)
+ {
+ var value = new Timestamp { Seconds = seconds, Nanos = nanoseconds };
+ value.ToDateTime();
+ }
+
+ private static void AssertRoundtrip(Timestamp timestamp, DateTime dateTime)
+ {
+ Assert.AreEqual(timestamp, Timestamp.FromDateTime(dateTime));
+ Assert.AreEqual(dateTime, timestamp.ToDateTime());
+ Assert.AreEqual(DateTimeKind.Utc, timestamp.ToDateTime().Kind);
+ }
+
+ [Test]
+ public void Arithmetic()
+ {
+ Timestamp t1 = new Timestamp { Seconds = 10000, Nanos = 5000 };
+ Timestamp t2 = new Timestamp { Seconds = 8000, Nanos = 10000 };
+ Duration difference = new Duration { Seconds = 1999, Nanos = Duration.NanosecondsPerSecond - 5000 };
+ Assert.AreEqual(difference, t1 - t2);
+ Assert.AreEqual(-difference, t2 - t1);
+
+ Assert.AreEqual(t1, t2 + difference);
+ Assert.AreEqual(t2, t1 - difference);
+ }
+
+ [Test]
+ public void ToString_NonNormalized()
+ {
+ // Just a single example should be sufficient...
+ var duration = new Timestamp { Seconds = 1, Nanos = -1 };
+ Assert.AreEqual("{ \"@warning\": \"Invalid Timestamp\", \"seconds\": \"1\", \"nanos\": -1 }", duration.ToString());
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs
new file mode 100644
index 00000000..5b7185dc
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs
@@ -0,0 +1,421 @@
+#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 System;
+using Google.Protobuf.TestProtos;
+using NUnit.Framework;
+using System.Collections;
+using System.IO;
+
+namespace Google.Protobuf.WellKnownTypes
+{
+ public class WrappersTest
+ {
+ [Test]
+ public void NullIsDefault()
+ {
+ var message = new TestWellKnownTypes();
+ Assert.IsNull(message.StringField);
+ Assert.IsNull(message.BytesField);
+ Assert.IsNull(message.BoolField);
+ Assert.IsNull(message.FloatField);
+ Assert.IsNull(message.DoubleField);
+ Assert.IsNull(message.Int32Field);
+ Assert.IsNull(message.Int64Field);
+ Assert.IsNull(message.Uint32Field);
+ Assert.IsNull(message.Uint64Field);
+ }
+
+ [Test]
+ public void NonDefaultSingleValues()
+ {
+ var message = new TestWellKnownTypes
+ {
+ StringField = "x",
+ BytesField = ByteString.CopyFrom(1, 2, 3),
+ BoolField = true,
+ FloatField = 12.5f,
+ DoubleField = 12.25d,
+ Int32Field = 1,
+ Int64Field = 2,
+ Uint32Field = 3,
+ Uint64Field = 4
+ };
+
+ var bytes = message.ToByteArray();
+ var parsed = TestWellKnownTypes.Parser.ParseFrom(bytes);
+
+ Assert.AreEqual("x", parsed.StringField);
+ Assert.AreEqual(ByteString.CopyFrom(1, 2, 3), parsed.BytesField);
+ Assert.AreEqual(true, parsed.BoolField);
+ Assert.AreEqual(12.5f, parsed.FloatField);
+ Assert.AreEqual(12.25d, parsed.DoubleField);
+ Assert.AreEqual(1, parsed.Int32Field);
+ Assert.AreEqual(2L, parsed.Int64Field);
+ Assert.AreEqual(3U, parsed.Uint32Field);
+ Assert.AreEqual(4UL, parsed.Uint64Field);
+ }
+
+ [Test]
+ public void NonNullDefaultIsPreservedThroughSerialization()
+ {
+ var message = new TestWellKnownTypes
+ {
+ StringField = "",
+ BytesField = ByteString.Empty,
+ BoolField = false,
+ FloatField = 0f,
+ DoubleField = 0d,
+ Int32Field = 0,
+ Int64Field = 0,
+ Uint32Field = 0,
+ Uint64Field = 0
+ };
+
+ var bytes = message.ToByteArray();
+ var parsed = TestWellKnownTypes.Parser.ParseFrom(bytes);
+
+ Assert.AreEqual("", parsed.StringField);
+ Assert.AreEqual(ByteString.Empty, parsed.BytesField);
+ Assert.AreEqual(false, parsed.BoolField);
+ Assert.AreEqual(0f, parsed.FloatField);
+ Assert.AreEqual(0d, parsed.DoubleField);
+ Assert.AreEqual(0, parsed.Int32Field);
+ Assert.AreEqual(0L, parsed.Int64Field);
+ Assert.AreEqual(0U, parsed.Uint32Field);
+ Assert.AreEqual(0UL, parsed.Uint64Field);
+ }
+
+ [Test]
+ public void RepeatedWrappersProhibitNullItems()
+ {
+ var message = new RepeatedWellKnownTypes();
+ Assert.Throws<ArgumentNullException>(() => message.BoolField.Add((bool?) null));
+ Assert.Throws<ArgumentNullException>(() => message.Int32Field.Add((int?) null));
+ Assert.Throws<ArgumentNullException>(() => message.StringField.Add((string) null));
+ Assert.Throws<ArgumentNullException>(() => message.BytesField.Add((ByteString) null));
+ }
+
+ [Test]
+ public void RepeatedWrappersSerializeDeserialize()
+ {
+ var message = new RepeatedWellKnownTypes
+ {
+ BoolField = { true, false },
+ BytesField = { ByteString.CopyFrom(1, 2, 3), ByteString.CopyFrom(4, 5, 6), ByteString.Empty },
+ DoubleField = { 12.5, -1.5, 0d },
+ FloatField = { 123.25f, -20f, 0f },
+ Int32Field = { int.MaxValue, int.MinValue, 0 },
+ Int64Field = { long.MaxValue, long.MinValue, 0L },
+ StringField = { "First", "Second", "" },
+ Uint32Field = { uint.MaxValue, uint.MinValue, 0U },
+ Uint64Field = { ulong.MaxValue, ulong.MinValue, 0UL },
+ };
+ var bytes = message.ToByteArray();
+ var parsed = RepeatedWellKnownTypes.Parser.ParseFrom(bytes);
+
+ Assert.AreEqual(message, parsed);
+ // Just to test a single value for sanity...
+ Assert.AreEqual("Second", message.StringField[1]);
+ }
+
+ [Test]
+ public void RepeatedWrappersBinaryFormat()
+ {
+ // At one point we accidentally used a packed format for repeated wrappers, which is wrong (and weird).
+ // This test is just to prove that we use the right format.
+
+ var rawOutput = new MemoryStream();
+ var output = new CodedOutputStream(rawOutput);
+ // Write a value of 5
+ output.WriteTag(RepeatedWellKnownTypes.Int32FieldFieldNumber, WireFormat.WireType.LengthDelimited);
+ output.WriteLength(2);
+ output.WriteTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint);
+ output.WriteInt32(5);
+ // Write a value of 0 (empty message)
+ output.WriteTag(RepeatedWellKnownTypes.Int32FieldFieldNumber, WireFormat.WireType.LengthDelimited);
+ output.WriteLength(0);
+ output.Flush();
+ var expectedBytes = rawOutput.ToArray();
+
+ var message = new RepeatedWellKnownTypes { Int32Field = { 5, 0 } };
+ var actualBytes = message.ToByteArray();
+ Assert.AreEqual(expectedBytes, actualBytes);
+ }
+
+ [Test]
+ public void MapWrappersSerializeDeserialize()
+ {
+ // Note: no null values here, as they are prohibited in map fields
+ // (despite being representable).
+ var message = new MapWellKnownTypes
+ {
+ BoolField = { { 10, false }, { 20, true } },
+ BytesField = {
+ { -1, ByteString.CopyFrom(1, 2, 3) },
+ { 10, ByteString.CopyFrom(4, 5, 6) },
+ { 1000, ByteString.Empty },
+ },
+ DoubleField = { { 1, 12.5 }, { 10, -1.5 }, { 20, 0d } },
+ FloatField = { { 2, 123.25f }, { 3, -20f }, { 4, 0f } },
+ Int32Field = { { 5, int.MaxValue }, { 6, int.MinValue }, { 7, 0 } },
+ Int64Field = { { 8, long.MaxValue }, { 9, long.MinValue }, { 10, 0L } },
+ StringField = { { 11, "First" }, { 12, "Second" }, { 13, "" } },
+ Uint32Field = { { 15, uint.MaxValue }, { 16, uint.MinValue }, { 17, 0U } },
+ Uint64Field = { { 18, ulong.MaxValue }, { 19, ulong.MinValue }, { 20, 0UL } },
+ };
+
+ var bytes = message.ToByteArray();
+ var parsed = MapWellKnownTypes.Parser.ParseFrom(bytes);
+
+ Assert.AreEqual(message, parsed);
+ // Just to test a single value for sanity...
+ Assert.AreEqual("Second", message.StringField[12]);
+ }
+
+ [Test]
+ public void Reflection_SingleValues()
+ {
+ var message = new TestWellKnownTypes
+ {
+ StringField = "x",
+ BytesField = ByteString.CopyFrom(1, 2, 3),
+ BoolField = true,
+ FloatField = 12.5f,
+ DoubleField = 12.25d,
+ Int32Field = 1,
+ Int64Field = 2,
+ Uint32Field = 3,
+ Uint64Field = 4
+ };
+ var fields = TestWellKnownTypes.Descriptor.Fields;
+
+ Assert.AreEqual("x", fields[TestWellKnownTypes.StringFieldFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(ByteString.CopyFrom(1, 2, 3), fields[TestWellKnownTypes.BytesFieldFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(true, fields[TestWellKnownTypes.BoolFieldFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(12.5f, fields[TestWellKnownTypes.FloatFieldFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(12.25d, fields[TestWellKnownTypes.DoubleFieldFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(1, fields[TestWellKnownTypes.Int32FieldFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(2L, fields[TestWellKnownTypes.Int64FieldFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(3U, fields[TestWellKnownTypes.Uint32FieldFieldNumber].Accessor.GetValue(message));
+ Assert.AreEqual(4UL, fields[TestWellKnownTypes.Uint64FieldFieldNumber].Accessor.GetValue(message));
+
+ // And a couple of null fields...
+ message.StringField = null;
+ message.FloatField = null;
+ Assert.IsNull(fields[TestWellKnownTypes.StringFieldFieldNumber].Accessor.GetValue(message));
+ Assert.IsNull(fields[TestWellKnownTypes.FloatFieldFieldNumber].Accessor.GetValue(message));
+ }
+
+ [Test]
+ public void Reflection_RepeatedFields()
+ {
+ // Just a single example... note that we can't have a null value here
+ var message = new RepeatedWellKnownTypes { Int32Field = { 1, 2 } };
+ var fields = RepeatedWellKnownTypes.Descriptor.Fields;
+ var list = (IList) fields[RepeatedWellKnownTypes.Int32FieldFieldNumber].Accessor.GetValue(message);
+ CollectionAssert.AreEqual(new[] { 1, 2 }, list);
+ }
+
+ [Test]
+ public void Reflection_MapFields()
+ {
+ // Just a single example... note that we can't have a null value here despite the value type being int?
+ var message = new MapWellKnownTypes { Int32Field = { { 1, 2 } } };
+ var fields = MapWellKnownTypes.Descriptor.Fields;
+ var dictionary = (IDictionary) fields[MapWellKnownTypes.Int32FieldFieldNumber].Accessor.GetValue(message);
+ Assert.AreEqual(2, dictionary[1]);
+ }
+
+ [Test]
+ public void Oneof()
+ {
+ var message = new OneofWellKnownTypes { EmptyField = new Empty() };
+ // Start off with a non-wrapper
+ Assert.AreEqual(OneofWellKnownTypes.OneofFieldOneofCase.EmptyField, message.OneofFieldCase);
+ AssertOneofRoundTrip(message);
+
+ message.StringField = "foo";
+ Assert.AreEqual(OneofWellKnownTypes.OneofFieldOneofCase.StringField, message.OneofFieldCase);
+ AssertOneofRoundTrip(message);
+
+ message.StringField = "foo";
+ Assert.AreEqual(OneofWellKnownTypes.OneofFieldOneofCase.StringField, message.OneofFieldCase);
+ AssertOneofRoundTrip(message);
+
+ message.DoubleField = 0.0f;
+ Assert.AreEqual(OneofWellKnownTypes.OneofFieldOneofCase.DoubleField, message.OneofFieldCase);
+ AssertOneofRoundTrip(message);
+
+ message.DoubleField = 1.0f;
+ Assert.AreEqual(OneofWellKnownTypes.OneofFieldOneofCase.DoubleField, message.OneofFieldCase);
+ AssertOneofRoundTrip(message);
+
+ message.ClearOneofField();
+ Assert.AreEqual(OneofWellKnownTypes.OneofFieldOneofCase.None, message.OneofFieldCase);
+ AssertOneofRoundTrip(message);
+ }
+
+ private void AssertOneofRoundTrip(OneofWellKnownTypes message)
+ {
+ // Normal roundtrip, but explicitly checking the case...
+ var bytes = message.ToByteArray();
+ var parsed = OneofWellKnownTypes.Parser.ParseFrom(bytes);
+ Assert.AreEqual(message, parsed);
+ Assert.AreEqual(message.OneofFieldCase, parsed.OneofFieldCase);
+ }
+
+ [Test]
+ [TestCase("x", "y", "y")]
+ [TestCase("x", "", "x")]
+ [TestCase("x", null, "x")]
+ [TestCase("", "y", "y")]
+ [TestCase("", "", "")]
+ [TestCase("", null, "")]
+ [TestCase(null, "y", "y")]
+ [TestCase(null, "", "")]
+ [TestCase(null, null, null)]
+ public void Merging(string original, string merged, string expected)
+ {
+ var originalMessage = new TestWellKnownTypes { StringField = original };
+ var mergingMessage = new TestWellKnownTypes { StringField = merged };
+ originalMessage.MergeFrom(mergingMessage);
+ Assert.AreEqual(expected, originalMessage.StringField);
+
+ // Try it using MergeFrom(CodedInputStream) too...
+ originalMessage = new TestWellKnownTypes { StringField = original };
+ originalMessage.MergeFrom(mergingMessage.ToByteArray());
+ Assert.AreEqual(expected, originalMessage.StringField);
+ }
+
+ // Merging is odd with wrapper types, due to the way that default values aren't emitted in
+ // the binary stream. In fact we cheat a little bit - a message with an explicitly present default
+ // value will have that default value ignored. See issue 615. Fixing this would require significant upheaval to
+ // the FieldCodec side of things.
+ [Test]
+ public void MergingStreamExplicitValue()
+ {
+ var message = new TestWellKnownTypes { Int32Field = 5 };
+
+ // Create a byte array which has the data of an Int32Value explicitly containing a value of 0.
+ // This wouldn't normally happen.
+ byte[] bytes;
+ var wrapperTag = WireFormat.MakeTag(TestWellKnownTypes.Int32FieldFieldNumber, WireFormat.WireType.LengthDelimited);
+ var valueTag = WireFormat.MakeTag(Int32Value.ValueFieldNumber, WireFormat.WireType.Varint);
+ using (var stream = new MemoryStream())
+ {
+ var coded = new CodedOutputStream(stream);
+ coded.WriteTag(wrapperTag);
+ coded.WriteLength(2); // valueTag + a value 0, each one byte
+ coded.WriteTag(valueTag);
+ coded.WriteInt32(0);
+ coded.Flush();
+ bytes = stream.ToArray();
+ }
+
+ message.MergeFrom(bytes);
+ // A normal implementation would have 0 now, as the explicit default would have been overwritten the 5.
+ // With the FieldCodec for Nullable<int>, we can't tell the difference between an implicit 0 and an explicit 0.
+ Assert.AreEqual(5, message.Int32Field);
+ }
+
+ [Test]
+ public void MergingStreamNoValue()
+ {
+ var message = new TestWellKnownTypes { Int32Field = 5 };
+
+ // Create a byte array which an Int32 field, but with no value.
+ var bytes = new TestWellKnownTypes { Int32Field = 0 }.ToByteArray();
+ Assert.AreEqual(2, bytes.Length); // The tag for Int32Field is a single byte, then a byte indicating a 0-length message.
+ message.MergeFrom(bytes);
+
+ // The "implicit" 0 did *not* overwrite the value.
+ // (This is the correct behaviour.)
+ Assert.AreEqual(5, message.Int32Field);
+ }
+
+ // All permutations of origin/merging value being null, zero (default) or non-default.
+ // As this is the in-memory version, we don't need to worry about the difference between implicit and explicit 0.
+ [Test]
+ [TestCase(null, null, null)]
+ [TestCase(null, 0, 0)]
+ [TestCase(null, 5, 5)]
+ [TestCase(0, null, 0)]
+ [TestCase(0, 0, 0)]
+ [TestCase(0, 5, 5)]
+ [TestCase(5, null, 5)]
+ [TestCase(5, 0, 5)]
+ [TestCase(5, 10, 10)]
+ public void MergingMessageWithZero(int? originValue, int? mergingValue, int? expectedResult)
+ {
+ // This differs from the MergingStreamCornerCase because when we merge message *objects*,
+ // we ignore default values from the "source".
+ var message1 = new TestWellKnownTypes { Int32Field = originValue };
+ var message2 = new TestWellKnownTypes { Int32Field = mergingValue };
+ message1.MergeFrom(message2);
+ Assert.AreEqual(expectedResult, message1.Int32Field);
+ }
+
+ [Test]
+ public void UnknownFieldInWrapper()
+ {
+ var stream = new MemoryStream();
+ var output = new CodedOutputStream(stream);
+ var wrapperTag = WireFormat.MakeTag(TestWellKnownTypes.Int32FieldFieldNumber, WireFormat.WireType.LengthDelimited);
+ var unknownTag = WireFormat.MakeTag(15, WireFormat.WireType.Varint);
+ var valueTag = WireFormat.MakeTag(Int32Value.ValueFieldNumber, WireFormat.WireType.Varint);
+
+ output.WriteTag(wrapperTag);
+ output.WriteLength(4); // unknownTag + value 5 + valueType + value 6, each 1 byte
+ output.WriteTag(unknownTag);
+ output.WriteInt32((int) valueTag); // Sneakily "pretend" it's a tag when it's really a value
+ output.WriteTag(valueTag);
+ output.WriteInt32(6);
+
+ output.Flush();
+ stream.Position = 0;
+
+ var message = TestWellKnownTypes.Parser.ParseFrom(stream);
+ Assert.AreEqual(6, message.Int32Field);
+ }
+
+ [Test]
+ public void ClearWithReflection()
+ {
+ // String and Bytes are the tricky ones here, as the CLR type of the property
+ // is the same between the wrapper and non-wrapper types.
+ var message = new TestWellKnownTypes { StringField = "foo" };
+ TestWellKnownTypes.Descriptor.Fields[TestWellKnownTypes.StringFieldFieldNumber].Accessor.Clear(message);
+ Assert.IsNull(message.StringField);
+ }
+ }
+}
diff --git a/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/project.json b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/project.json
new file mode 100644
index 00000000..87b732c9
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/project.json
@@ -0,0 +1,44 @@
+{
+ "buildOptions": {
+ "debugType": "portable",
+ "keyFile": "../../keys/Google.Protobuf.snk"
+ },
+
+ "configurations": {
+ "Debug": {
+ "buildOptions": {
+ "define": [ "DEBUG", "TRACE" ]
+ }
+ },
+ "Release": {
+ "buildOptions": {
+ "define": [ "RELEASE", "TRACE" ],
+ "optimize": true
+ }
+ }
+ },
+
+ "dependencies": {
+ "Google.Protobuf": { "target": "project" },
+ "NUnit": "3.4.0",
+ "dotnet-test-nunit": "3.4.0-alpha-2",
+ },
+
+ "testRunner": "nunit",
+
+ "frameworks": {
+ "netcoreapp1.0": {
+ "imports" : [ "dnxcore50", "netcoreapp1.0", "portable-net45+win8" ],
+ "buildOptions": {
+ "define": [ "PCL" ]
+ },
+ "dependencies": {
+ "Microsoft.NETCore.App": {
+ "version": "1.0.0",
+ "type": "platform"
+ },
+ "System.Console": "4.0.0"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/csharp/compatibility_tests/v3.0.0/test.sh b/csharp/compatibility_tests/v3.0.0/test.sh
new file mode 100755
index 00000000..bb04fc2c
--- /dev/null
+++ b/csharp/compatibility_tests/v3.0.0/test.sh
@@ -0,0 +1,102 @@
+#!/bin/bash
+
+function run_test() {
+ # Generate test proto files.
+ ./protoc_1 -Iprotos/src -I../../../src/ --csharp_out=src/Google.Protobuf.Test \
+ --csharp_opt=base_namespace=Google.Protobuf \
+ protos/src/google/protobuf/unittest_import_proto3.proto \
+ protos/src/google/protobuf/unittest_import_public_proto3.proto \
+ protos/src/google/protobuf/unittest_well_known_types.proto
+
+ ./protoc_1 -Iprotos/csharp --csharp_out=src/Google.Protobuf.Test \
+ --csharp_opt=base_namespace=UnitTest.Issues \
+ protos/csharp/protos/unittest_issues.proto
+
+ ./protoc_2 -Iprotos/src --csharp_out=src/Google.Protobuf.Test \
+ --csharp_opt=base_namespace=Google.Protobuf \
+ protos/src/google/protobuf/unittest_proto3.proto \
+ protos/src/google/protobuf/map_unittest_proto3.proto
+
+ # Build and test.
+ dotnet build -c release src/Google.Protobuf src/Google.Protobuf.Test
+ dotnet test -c release -f netcoreapp1.0 src/Google.Protobuf.Test
+}
+
+set -ex
+
+# Change to the script's directory.
+cd $(dirname $0)
+
+# Version of the tests (i.e., the version of protobuf from where we extracted
+# these tests).
+TEST_VERSION=3.0.0
+
+# The old version of protobuf that we are testing compatibility against. This
+# is usually the same as TEST_VERSION (i.e., we use the tests extracted from
+# that version to test compatibility of the newest runtime against it), but it
+# is also possible to use this same test set to test the compatibiilty of the
+# latest version against other versions.
+case "$1" in
+ ""|3.0.0)
+ OLD_VERSION=3.0.0
+ OLD_VERSION_PROTOC=http://repo1.maven.org/maven2/com/google/protobuf/protoc/3.0.0/protoc-3.0.0-linux-x86_64.exe
+ ;;
+ 3.0.2)
+ OLD_VERSION=3.0.2
+ OLD_VERSION_PROTOC=http://repo1.maven.org/maven2/com/google/protobuf/protoc/3.0.2/protoc-3.0.2-linux-x86_64.exe
+ ;;
+ 3.1.0)
+ OLD_VERSION=3.1.0
+ OLD_VERSION_PROTOC=http://repo1.maven.org/maven2/com/google/protobuf/protoc/3.1.0/protoc-3.1.0-linux-x86_64.exe
+ ;;
+ *)
+ echo "[ERROR]: Unknown version number: $1"
+ exit 1
+ ;;
+esac
+
+echo "Running compatibility tests with $OLD_VERSION"
+
+# Check protoc
+[ -f ../../../src/protoc ] || {
+ echo "[ERROR]: Please build protoc first."
+ exit 1
+}
+
+# Download old version protoc compiler (for linux).
+wget $OLD_VERSION_PROTOC -O old_protoc
+chmod +x old_protoc
+
+# Test source compatibility. In these tests we recompile everything against
+# the new runtime (including old version generated code).
+# Copy the new runtime and keys.
+cp ../../src/Google.Protobuf src/Google.Protobuf -r
+cp ../../keys . -r
+dotnet restore
+
+# Test A.1:
+# proto set 1: use old version
+# proto set 2 which may import protos in set 1: use old version
+cp old_protoc protoc_1
+cp old_protoc protoc_2
+run_test
+
+# Test A.2:
+# proto set 1: use new version
+# proto set 2 which may import protos in set 1: use old version
+cp ../../../src/protoc protoc_1
+cp old_protoc protoc_2
+run_test
+
+# Test A.3:
+# proto set 1: use old version
+# proto set 2 which may import protos in set 1: use new version
+cp old_protoc protoc_1
+cp ../../../src/protoc protoc_2
+run_test
+
+rm protoc_1
+rm protoc_2
+rm old_protoc
+rm keys -r
+rm src/Google.Protobuf -r
diff --git a/tests.sh b/tests.sh
index d0d77e49..981d20cc 100755
--- a/tests.sh
+++ b/tests.sh
@@ -63,7 +63,7 @@ build_cpp_distcheck() {
# List all files that should be included in the distribution package.
git ls-files | grep "^\(java\|python\|objectivec\|csharp\|js\|ruby\|php\|cmake\|examples\)" |\
grep -v ".gitignore" | grep -v "java/compatibility_tests" |\
- grep -v "python/compatibility_tests" > dist.lst
+ grep -v "python/compatibility_tests" | grep -v "csharp/compatibility_tests" > dist.lst
# Unzip the dist tar file.
DIST=`ls *.tar.gz`
tar -xf $DIST
@@ -119,6 +119,9 @@ build_csharp() {
(cd csharp/src; dotnet restore)
csharp/buildall.sh
cd conformance && make test_csharp && cd ..
+
+ # Run csharp compatibility test between 3.0.0 and the current version.
+ csharp/compatibility_tests/v3.0.0/test.sh 3.0.0
}
build_golang() {