diff options
author | Feng Xiao <xfxyjwf@gmail.com> | 2015-08-25 14:32:09 -0700 |
---|---|---|
committer | Feng Xiao <xfxyjwf@gmail.com> | 2015-08-25 14:32:09 -0700 |
commit | fbb3ef28c91026bd56585cb825f40a563301466e (patch) | |
tree | f149dc5bf45c76531fbae539dd94544dd3f027a1 /java/util/src/test/java/com/google/protobuf | |
parent | 839b180dbae98adf6caa54d0fb87b8d0a43081dc (diff) |
Merge Java util package to github.
Diffstat (limited to 'java/util/src/test/java/com/google/protobuf')
5 files changed, 1937 insertions, 0 deletions
diff --git a/java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java b/java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java new file mode 100644 index 00000000..3391f239 --- /dev/null +++ b/java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java @@ -0,0 +1,229 @@ +// 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. + +package com.google.protobuf.util; + +import protobuf_unittest.UnittestProto.NestedTestAllTypes; +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage; + +import junit.framework.TestCase; + +public class FieldMaskTreeTest extends TestCase { + public void testAddFieldPath() throws Exception { + FieldMaskTree tree = new FieldMaskTree(); + assertEquals("", tree.toString()); + tree.addFieldPath(""); + assertEquals("", tree.toString()); + // New branch. + tree.addFieldPath("foo"); + assertEquals("foo", tree.toString()); + // Redundant path. + tree.addFieldPath("foo"); + assertEquals("foo", tree.toString()); + // New branch. + tree.addFieldPath("bar.baz"); + assertEquals("bar.baz,foo", tree.toString()); + // Redundant sub-path. + tree.addFieldPath("foo.bar"); + assertEquals("bar.baz,foo", tree.toString()); + // New branch from a non-root node. + tree.addFieldPath("bar.quz"); + assertEquals("bar.baz,bar.quz,foo", tree.toString()); + // A path that matches several existing sub-paths. + tree.addFieldPath("bar"); + assertEquals("bar,foo", tree.toString()); + } + + public void testMergeFromFieldMask() throws Exception { + FieldMaskTree tree = new FieldMaskTree( + FieldMaskUtil.fromString("foo,bar.baz,bar.quz")); + assertEquals("bar.baz,bar.quz,foo", tree.toString()); + tree.mergeFromFieldMask( + FieldMaskUtil.fromString("foo.bar,bar")); + assertEquals("bar,foo", tree.toString()); + } + + public void testIntersectFieldPath() throws Exception { + FieldMaskTree tree = new FieldMaskTree( + FieldMaskUtil.fromString("foo,bar.baz,bar.quz")); + FieldMaskTree result = new FieldMaskTree(); + // Empty path. + tree.intersectFieldPath("", result); + assertEquals("", result.toString()); + // Non-exist path. + tree.intersectFieldPath("quz", result); + assertEquals("", result.toString()); + // Sub-path of an existing leaf. + tree.intersectFieldPath("foo.bar", result); + assertEquals("foo.bar", result.toString()); + // Match an existing leaf node. + tree.intersectFieldPath("foo", result); + assertEquals("foo", result.toString()); + // Non-exist path. + tree.intersectFieldPath("bar.foo", result); + assertEquals("foo", result.toString()); + // Match a non-leaf node. + tree.intersectFieldPath("bar", result); + assertEquals("bar.baz,bar.quz,foo", result.toString()); + } + + public void testMerge() throws Exception { + TestAllTypes value = TestAllTypes.newBuilder() + .setOptionalInt32(1234) + .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678)) + .addRepeatedInt32(4321) + .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)) + .build(); + NestedTestAllTypes source = NestedTestAllTypes.newBuilder() + .setPayload(value) + .setChild(NestedTestAllTypes.newBuilder().setPayload(value)) + .build(); + // Now we have a message source with the following structure: + // [root] -+- payload -+- optional_int32 + // | +- optional_nested_message + // | +- repeated_int32 + // | +- repeated_nested_message + // | + // +- child --- payload -+- optional_int32 + // +- optional_nested_message + // +- repeated_int32 + // +- repeated_nested_message + + FieldMaskUtil.MergeOptions options = new FieldMaskUtil.MergeOptions(); + + // Test merging each individual field. + NestedTestAllTypes.Builder builder = NestedTestAllTypes.newBuilder(); + new FieldMaskTree().addFieldPath("payload.optional_int32") + .merge(source, builder, options); + NestedTestAllTypes.Builder expected = NestedTestAllTypes.newBuilder(); + expected.getPayloadBuilder().setOptionalInt32(1234); + assertEquals(expected.build(), builder.build()); + + builder = NestedTestAllTypes.newBuilder(); + new FieldMaskTree().addFieldPath("payload.optional_nested_message") + .merge(source, builder, options); + expected = NestedTestAllTypes.newBuilder(); + expected.getPayloadBuilder().setOptionalNestedMessage( + NestedMessage.newBuilder().setBb(5678)); + assertEquals(expected.build(), builder.build()); + + + builder = NestedTestAllTypes.newBuilder(); + new FieldMaskTree().addFieldPath("payload.repeated_int32") + .merge(source, builder, options); + expected = NestedTestAllTypes.newBuilder(); + expected.getPayloadBuilder().addRepeatedInt32(4321); + assertEquals(expected.build(), builder.build()); + + builder = NestedTestAllTypes.newBuilder(); + new FieldMaskTree().addFieldPath("payload.repeated_nested_message") + .merge(source, builder, options); + expected = NestedTestAllTypes.newBuilder(); + expected.getPayloadBuilder().addRepeatedNestedMessage( + NestedMessage.newBuilder().setBb(8765)); + assertEquals(expected.build(), builder.build()); + + builder = NestedTestAllTypes.newBuilder(); + new FieldMaskTree().addFieldPath("child.payload.optional_int32") + .merge(source, builder, options); + expected = NestedTestAllTypes.newBuilder(); + expected.getChildBuilder().getPayloadBuilder().setOptionalInt32(1234); + assertEquals(expected.build(), builder.build()); + + builder = NestedTestAllTypes.newBuilder(); + new FieldMaskTree().addFieldPath("child.payload.optional_nested_message") + .merge(source, builder, options); + expected = NestedTestAllTypes.newBuilder(); + expected.getChildBuilder().getPayloadBuilder().setOptionalNestedMessage( + NestedMessage.newBuilder().setBb(5678)); + assertEquals(expected.build(), builder.build()); + + + builder = NestedTestAllTypes.newBuilder(); + new FieldMaskTree().addFieldPath("child.payload.repeated_int32") + .merge(source, builder, options); + expected = NestedTestAllTypes.newBuilder(); + expected.getChildBuilder().getPayloadBuilder().addRepeatedInt32(4321); + assertEquals(expected.build(), builder.build()); + + + builder = NestedTestAllTypes.newBuilder(); + new FieldMaskTree().addFieldPath("child.payload.repeated_nested_message") + .merge(source, builder, options); + expected = NestedTestAllTypes.newBuilder(); + expected.getChildBuilder().getPayloadBuilder().addRepeatedNestedMessage( + NestedMessage.newBuilder().setBb(8765)); + assertEquals(expected.build(), builder.build()); + + // Test merging all fields. + builder = NestedTestAllTypes.newBuilder(); + new FieldMaskTree().addFieldPath("child").addFieldPath("payload") + .merge(source, builder, options); + assertEquals(source, builder.build()); + + // Test repeated options. + builder = NestedTestAllTypes.newBuilder(); + builder.getPayloadBuilder().addRepeatedInt32(1000); + new FieldMaskTree().addFieldPath("payload.repeated_int32") + .merge(source, builder, options); + // Default behavior is to append repeated fields. + assertEquals(2, builder.getPayload().getRepeatedInt32Count()); + assertEquals(1000, builder.getPayload().getRepeatedInt32(0)); + assertEquals(4321, builder.getPayload().getRepeatedInt32(1)); + // Change to replace repeated fields. + options.setReplaceRepeatedFields(true); + new FieldMaskTree().addFieldPath("payload.repeated_int32") + .merge(source, builder, options); + assertEquals(1, builder.getPayload().getRepeatedInt32Count()); + assertEquals(4321, builder.getPayload().getRepeatedInt32(0)); + + // Test message options. + builder = NestedTestAllTypes.newBuilder(); + builder.getPayloadBuilder().setOptionalInt32(1000); + builder.getPayloadBuilder().setOptionalUint32(2000); + new FieldMaskTree().addFieldPath("payload") + .merge(source, builder, options); + // Default behavior is to merge message fields. + assertEquals(1234, builder.getPayload().getOptionalInt32()); + assertEquals(2000, builder.getPayload().getOptionalUint32()); + + // Change to replace message fields. + options.setReplaceMessageFields(true); + builder = NestedTestAllTypes.newBuilder(); + builder.getPayloadBuilder().setOptionalInt32(1000); + builder.getPayloadBuilder().setOptionalUint32(2000); + new FieldMaskTree().addFieldPath("payload") + .merge(source, builder, options); + assertEquals(1234, builder.getPayload().getOptionalInt32()); + assertEquals(0, builder.getPayload().getOptionalUint32()); + } +} + diff --git a/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java b/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java new file mode 100644 index 00000000..67fbe0b1 --- /dev/null +++ b/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java @@ -0,0 +1,135 @@ +// 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. + +package com.google.protobuf.util; + +import com.google.protobuf.FieldMask; +import protobuf_unittest.UnittestProto.NestedTestAllTypes; +import protobuf_unittest.UnittestProto.TestAllTypes; + +import junit.framework.TestCase; + +/** Unit tests for {@link FieldMaskUtil}. */ +public class FieldMaskUtilTest extends TestCase { + public void testIsValid() throws Exception { + assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload")); + assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.class, "nonexist")); + assertTrue(FieldMaskUtil.isValid( + NestedTestAllTypes.class, "payload.optional_int32")); + assertTrue(FieldMaskUtil.isValid( + NestedTestAllTypes.class, "payload.repeated_int32")); + assertTrue(FieldMaskUtil.isValid( + NestedTestAllTypes.class, "payload.optional_nested_message")); + assertTrue(FieldMaskUtil.isValid( + NestedTestAllTypes.class, "payload.repeated_nested_message")); + assertFalse(FieldMaskUtil.isValid( + NestedTestAllTypes.class, "payload.nonexist")); + + assertTrue(FieldMaskUtil.isValid( + NestedTestAllTypes.class, "payload.optional_nested_message.bb")); + // Repeated fields cannot have sub-paths. + assertFalse(FieldMaskUtil.isValid( + NestedTestAllTypes.class, "payload.repeated_nested_message.bb")); + // Non-message fields cannot have sub-paths. + assertFalse(FieldMaskUtil.isValid( + NestedTestAllTypes.class, "payload.optional_int32.bb")); + } + + public void testToString() throws Exception { + assertEquals("", FieldMaskUtil.toString(FieldMask.getDefaultInstance())); + FieldMask mask = FieldMask.newBuilder().addPaths("foo").build(); + assertEquals("foo", FieldMaskUtil.toString(mask)); + mask = FieldMask.newBuilder().addPaths("foo").addPaths("bar").build(); + assertEquals("foo,bar", FieldMaskUtil.toString(mask)); + + // Empty field paths are ignored. + mask = FieldMask.newBuilder().addPaths("").addPaths("foo").addPaths(""). + addPaths("bar").addPaths("").build(); + assertEquals("foo,bar", FieldMaskUtil.toString(mask)); + } + + public void testFromString() throws Exception { + FieldMask mask = FieldMaskUtil.fromString(""); + assertEquals(0, mask.getPathsCount()); + mask = FieldMaskUtil.fromString("foo"); + assertEquals(1, mask.getPathsCount()); + assertEquals("foo", mask.getPaths(0)); + mask = FieldMaskUtil.fromString("foo,bar.baz"); + assertEquals(2, mask.getPathsCount()); + assertEquals("foo", mask.getPaths(0)); + assertEquals("bar.baz", mask.getPaths(1)); + + // Empty field paths are ignore. + mask = FieldMaskUtil.fromString(",foo,,bar,"); + assertEquals(2, mask.getPathsCount()); + assertEquals("foo", mask.getPaths(0)); + assertEquals("bar", mask.getPaths(1)); + + // Check whether the field paths are valid if a class parameter is provided. + mask = FieldMaskUtil.fromString(NestedTestAllTypes.class, ",payload"); + + try { + mask = FieldMaskUtil.fromString( + NestedTestAllTypes.class, "payload,nonexist"); + fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + public void testUnion() throws Exception { + // Only test a simple case here and expect + // {@link FieldMaskTreeTest#testAddFieldPath} to cover all scenarios. + FieldMask mask1 = FieldMaskUtil.fromString("foo,bar.baz,bar.quz"); + FieldMask mask2 = FieldMaskUtil.fromString("foo.bar,bar"); + FieldMask result = FieldMaskUtil.union(mask1, mask2); + assertEquals("bar,foo", FieldMaskUtil.toString(result)); + } + + public void testIntersection() throws Exception { + // Only test a simple case here and expect + // {@link FieldMaskTreeTest#testIntersectFieldPath} to cover all scenarios. + FieldMask mask1 = FieldMaskUtil.fromString("foo,bar.baz,bar.quz"); + FieldMask mask2 = FieldMaskUtil.fromString("foo.bar,bar"); + FieldMask result = FieldMaskUtil.intersection(mask1, mask2); + assertEquals("bar.baz,bar.quz,foo.bar", FieldMaskUtil.toString(result)); + } + + public void testMerge() throws Exception { + // Only test a simple case here and expect + // {@link FieldMaskTreeTest#testMerge} to cover all scenarios. + NestedTestAllTypes source = NestedTestAllTypes.newBuilder() + .setPayload(TestAllTypes.newBuilder().setOptionalInt32(1234)) + .build(); + NestedTestAllTypes.Builder builder = NestedTestAllTypes.newBuilder(); + FieldMaskUtil.merge(FieldMaskUtil.fromString("payload"), source, builder); + assertEquals(1234, builder.getPayload().getOptionalInt32()); + } +} diff --git a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java new file mode 100644 index 00000000..ddf5ad2a --- /dev/null +++ b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java @@ -0,0 +1,976 @@ +// 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. + +package com.google.protobuf.util; + +import com.google.protobuf.Any; +import com.google.protobuf.BoolValue; +import com.google.protobuf.ByteString; +import com.google.protobuf.BytesValue; +import com.google.protobuf.DoubleValue; +import com.google.protobuf.FloatValue; +import com.google.protobuf.Int32Value; +import com.google.protobuf.Int64Value; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.ListValue; +import com.google.protobuf.Message; +import com.google.protobuf.StringValue; +import com.google.protobuf.Struct; +import com.google.protobuf.UInt32Value; +import com.google.protobuf.UInt64Value; +import com.google.protobuf.Value; +import com.google.protobuf.util.JsonFormat.TypeRegistry; +import com.google.protobuf.util.JsonTestProto.TestAllTypes; +import com.google.protobuf.util.JsonTestProto.TestAllTypes.NestedEnum; +import com.google.protobuf.util.JsonTestProto.TestAllTypes.NestedMessage; +import com.google.protobuf.util.JsonTestProto.TestAny; +import com.google.protobuf.util.JsonTestProto.TestDuration; +import com.google.protobuf.util.JsonTestProto.TestFieldMask; +import com.google.protobuf.util.JsonTestProto.TestMap; +import com.google.protobuf.util.JsonTestProto.TestStruct; +import com.google.protobuf.util.JsonTestProto.TestTimestamp; +import com.google.protobuf.util.JsonTestProto.TestWrappers; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; + +public class JsonFormatTest extends TestCase { + private void setAllFields(TestAllTypes.Builder builder) { + builder.setOptionalInt32(1234); + builder.setOptionalInt64(1234567890123456789L); + builder.setOptionalUint32(5678); + builder.setOptionalUint64(2345678901234567890L); + builder.setOptionalSint32(9012); + builder.setOptionalSint64(3456789012345678901L); + builder.setOptionalFixed32(3456); + builder.setOptionalFixed64(4567890123456789012L); + builder.setOptionalSfixed32(7890); + builder.setOptionalSfixed64(5678901234567890123L); + builder.setOptionalFloat(1.5f); + builder.setOptionalDouble(1.25); + builder.setOptionalBool(true); + builder.setOptionalString("Hello world!"); + builder.setOptionalBytes(ByteString.copyFrom(new byte[]{0, 1, 2})); + builder.setOptionalNestedEnum(NestedEnum.BAR); + builder.getOptionalNestedMessageBuilder().setValue(100); + + builder.addRepeatedInt32(1234); + builder.addRepeatedInt64(1234567890123456789L); + builder.addRepeatedUint32(5678); + builder.addRepeatedUint64(2345678901234567890L); + builder.addRepeatedSint32(9012); + builder.addRepeatedSint64(3456789012345678901L); + builder.addRepeatedFixed32(3456); + builder.addRepeatedFixed64(4567890123456789012L); + builder.addRepeatedSfixed32(7890); + builder.addRepeatedSfixed64(5678901234567890123L); + builder.addRepeatedFloat(1.5f); + builder.addRepeatedDouble(1.25); + builder.addRepeatedBool(true); + builder.addRepeatedString("Hello world!"); + builder.addRepeatedBytes(ByteString.copyFrom(new byte[]{0, 1, 2})); + builder.addRepeatedNestedEnum(NestedEnum.BAR); + builder.addRepeatedNestedMessageBuilder().setValue(100); + + builder.addRepeatedInt32(234); + builder.addRepeatedInt64(234567890123456789L); + builder.addRepeatedUint32(678); + builder.addRepeatedUint64(345678901234567890L); + builder.addRepeatedSint32(012); + builder.addRepeatedSint64(456789012345678901L); + builder.addRepeatedFixed32(456); + builder.addRepeatedFixed64(567890123456789012L); + builder.addRepeatedSfixed32(890); + builder.addRepeatedSfixed64(678901234567890123L); + builder.addRepeatedFloat(11.5f); + builder.addRepeatedDouble(11.25); + builder.addRepeatedBool(true); + builder.addRepeatedString("ello world!"); + builder.addRepeatedBytes(ByteString.copyFrom(new byte[]{1, 2})); + builder.addRepeatedNestedEnum(NestedEnum.BAZ); + builder.addRepeatedNestedMessageBuilder().setValue(200); + } + + private void assertRoundTripEquals(Message message) throws Exception { + assertRoundTripEquals(message, TypeRegistry.getEmptyTypeRegistry()); + } + + private void assertRoundTripEquals(Message message, TypeRegistry registry) throws Exception { + JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry); + JsonFormat.Parser parser = JsonFormat.parser().usingTypeRegistry(registry); + Message.Builder builder = message.newBuilderForType(); + parser.merge(printer.print(message), builder); + Message parsedMessage = builder.build(); + assertEquals(message.toString(), parsedMessage.toString()); + } + + private String toJsonString(Message message) throws IOException { + return JsonFormat.printer().print(message); + } + + private void mergeFromJson(String json, Message.Builder builder) throws IOException { + JsonFormat.parser().merge(json, builder); + } + + public void testAllFields() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + setAllFields(builder); + TestAllTypes message = builder.build(); + + assertEquals( + "{\n" + + " \"optionalInt32\": 1234,\n" + + " \"optionalInt64\": \"1234567890123456789\",\n" + + " \"optionalUint32\": 5678,\n" + + " \"optionalUint64\": \"2345678901234567890\",\n" + + " \"optionalSint32\": 9012,\n" + + " \"optionalSint64\": \"3456789012345678901\",\n" + + " \"optionalFixed32\": 3456,\n" + + " \"optionalFixed64\": \"4567890123456789012\",\n" + + " \"optionalSfixed32\": 7890,\n" + + " \"optionalSfixed64\": \"5678901234567890123\",\n" + + " \"optionalFloat\": 1.5,\n" + + " \"optionalDouble\": 1.25,\n" + + " \"optionalBool\": true,\n" + + " \"optionalString\": \"Hello world!\",\n" + + " \"optionalBytes\": \"AAEC\",\n" + + " \"optionalNestedMessage\": {\n" + + " \"value\": 100\n" + + " },\n" + + " \"optionalNestedEnum\": \"BAR\",\n" + + " \"repeatedInt32\": [1234, 234],\n" + + " \"repeatedInt64\": [\"1234567890123456789\", \"234567890123456789\"],\n" + + " \"repeatedUint32\": [5678, 678],\n" + + " \"repeatedUint64\": [\"2345678901234567890\", \"345678901234567890\"],\n" + + " \"repeatedSint32\": [9012, 10],\n" + + " \"repeatedSint64\": [\"3456789012345678901\", \"456789012345678901\"],\n" + + " \"repeatedFixed32\": [3456, 456],\n" + + " \"repeatedFixed64\": [\"4567890123456789012\", \"567890123456789012\"],\n" + + " \"repeatedSfixed32\": [7890, 890],\n" + + " \"repeatedSfixed64\": [\"5678901234567890123\", \"678901234567890123\"],\n" + + " \"repeatedFloat\": [1.5, 11.5],\n" + + " \"repeatedDouble\": [1.25, 11.25],\n" + + " \"repeatedBool\": [true, true],\n" + + " \"repeatedString\": [\"Hello world!\", \"ello world!\"],\n" + + " \"repeatedBytes\": [\"AAEC\", \"AQI=\"],\n" + + " \"repeatedNestedMessage\": [{\n" + + " \"value\": 100\n" + + " }, {\n" + + " \"value\": 200\n" + + " }],\n" + + " \"repeatedNestedEnum\": [\"BAR\", \"BAZ\"]\n" + + "}", + toJsonString(message)); + + assertRoundTripEquals(message); + } + + public void testUnknownEnumValues() throws Exception { + // Unknown enum values will be dropped. + // TODO(xiaofeng): We may want to revisit this (whether we should omit + // unknown enum values). + TestAllTypes message = TestAllTypes.newBuilder() + .setOptionalNestedEnumValue(12345) + .addRepeatedNestedEnumValue(12345) + .addRepeatedNestedEnumValue(0) + .build(); + assertEquals( + "{\n" + + " \"repeatedNestedEnum\": [\"FOO\"]\n" + + "}", toJsonString(message)); + + TestMap.Builder mapBuilder = TestMap.newBuilder(); + mapBuilder.getMutableInt32ToEnumMapValue().put(1, 0); + mapBuilder.getMutableInt32ToEnumMapValue().put(2, 12345); + TestMap mapMessage = mapBuilder.build(); + assertEquals( + "{\n" + + " \"int32ToEnumMap\": {\n" + + " \"1\": \"FOO\"\n" + + " }\n" + + "}", toJsonString(mapMessage)); + } + + public void testSpecialFloatValues() throws Exception { + TestAllTypes message = TestAllTypes.newBuilder() + .addRepeatedFloat(Float.NaN) + .addRepeatedFloat(Float.POSITIVE_INFINITY) + .addRepeatedFloat(Float.NEGATIVE_INFINITY) + .addRepeatedDouble(Double.NaN) + .addRepeatedDouble(Double.POSITIVE_INFINITY) + .addRepeatedDouble(Double.NEGATIVE_INFINITY) + .build(); + assertEquals( + "{\n" + + " \"repeatedFloat\": [\"NaN\", \"Infinity\", \"-Infinity\"],\n" + + " \"repeatedDouble\": [\"NaN\", \"Infinity\", \"-Infinity\"]\n" + + "}", toJsonString(message)); + + assertRoundTripEquals(message); + } + + public void testParserAcceptStringForNumbericField() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + mergeFromJson( + "{\n" + + " \"optionalInt32\": \"1234\",\n" + + " \"optionalUint32\": \"5678\",\n" + + " \"optionalSint32\": \"9012\",\n" + + " \"optionalFixed32\": \"3456\",\n" + + " \"optionalSfixed32\": \"7890\",\n" + + " \"optionalFloat\": \"1.5\",\n" + + " \"optionalDouble\": \"1.25\",\n" + + " \"optionalBool\": \"true\"\n" + + "}", builder); + TestAllTypes message = builder.build(); + assertEquals(1234, message.getOptionalInt32()); + assertEquals(5678, message.getOptionalUint32()); + assertEquals(9012, message.getOptionalSint32()); + assertEquals(3456, message.getOptionalFixed32()); + assertEquals(7890, message.getOptionalSfixed32()); + assertEquals(1.5f, message.getOptionalFloat()); + assertEquals(1.25, message.getOptionalDouble()); + assertEquals(true, message.getOptionalBool()); + } + + private void assertRejects(String name, String value) { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + try { + // Numeric form is rejected. + mergeFromJson("{\"" + name + "\":" + value + "}", builder); + fail("Exception is expected."); + } catch (IOException e) { + // Expected. + } + try { + // String form is also rejected. + mergeFromJson("{\"" + name + "\":\"" + value + "\"}", builder); + fail("Exception is expected."); + } catch (IOException e) { + // Expected. + } + } + + private void assertAccepts(String name, String value) throws IOException { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + // Both numeric form and string form are accepted. + mergeFromJson("{\"" + name + "\":" + value + "}", builder); + mergeFromJson("{\"" + name + "\":\"" + value + "\"}", builder); + } + + public void testParserRejectOutOfRangeNumericValues() throws Exception { + assertAccepts("optionalInt32", String.valueOf(Integer.MAX_VALUE)); + assertAccepts("optionalInt32", String.valueOf(Integer.MIN_VALUE)); + assertRejects("optionalInt32", String.valueOf(Integer.MAX_VALUE + 1L)); + assertRejects("optionalInt32", String.valueOf(Integer.MIN_VALUE - 1L)); + + assertAccepts("optionalUint32", String.valueOf(Integer.MAX_VALUE + 1L)); + assertRejects("optionalUint32", "123456789012345"); + assertRejects("optionalUint32", "-1"); + + BigInteger one = new BigInteger("1"); + BigInteger maxLong = new BigInteger(String.valueOf(Long.MAX_VALUE)); + BigInteger minLong = new BigInteger(String.valueOf(Long.MIN_VALUE)); + assertAccepts("optionalInt64", maxLong.toString()); + assertAccepts("optionalInt64", minLong.toString()); + assertRejects("optionalInt64", maxLong.add(one).toString()); + assertRejects("optionalInt64", minLong.subtract(one).toString()); + + assertAccepts("optionalUint64", maxLong.add(one).toString()); + assertRejects("optionalUint64", "1234567890123456789012345"); + assertRejects("optionalUint64", "-1"); + + assertAccepts("optionalBool", "true"); + assertRejects("optionalBool", "1"); + assertRejects("optionalBool", "0"); + + assertAccepts("optionalFloat", String.valueOf(Float.MAX_VALUE)); + assertAccepts("optionalFloat", String.valueOf(-Float.MAX_VALUE)); + assertRejects("optionalFloat", String.valueOf(Double.MAX_VALUE)); + assertRejects("optionalFloat", String.valueOf(-Double.MAX_VALUE)); + + BigDecimal moreThanOne = new BigDecimal("1.000001"); + BigDecimal maxDouble = new BigDecimal(Double.MAX_VALUE); + BigDecimal minDouble = new BigDecimal(-Double.MAX_VALUE); + assertAccepts("optionalDouble", maxDouble.toString()); + assertAccepts("optionalDouble", minDouble.toString()); + assertRejects("optionalDouble", maxDouble.multiply(moreThanOne).toString()); + assertRejects("optionalDouble", minDouble.multiply(moreThanOne).toString()); + } + + public void testParserAcceptNull() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + mergeFromJson( + "{\n" + + " \"optionalInt32\": null,\n" + + " \"optionalInt64\": null,\n" + + " \"optionalUint32\": null,\n" + + " \"optionalUint64\": null,\n" + + " \"optionalSint32\": null,\n" + + " \"optionalSint64\": null,\n" + + " \"optionalFixed32\": null,\n" + + " \"optionalFixed64\": null,\n" + + " \"optionalSfixed32\": null,\n" + + " \"optionalSfixed64\": null,\n" + + " \"optionalFloat\": null,\n" + + " \"optionalDouble\": null,\n" + + " \"optionalBool\": null,\n" + + " \"optionalString\": null,\n" + + " \"optionalBytes\": null,\n" + + " \"optionalNestedMessage\": null,\n" + + " \"optionalNestedEnum\": null,\n" + + " \"repeatedInt32\": null,\n" + + " \"repeatedInt64\": null,\n" + + " \"repeatedUint32\": null,\n" + + " \"repeatedUint64\": null,\n" + + " \"repeatedSint32\": null,\n" + + " \"repeatedSint64\": null,\n" + + " \"repeatedFixed32\": null,\n" + + " \"repeatedFixed64\": null,\n" + + " \"repeatedSfixed32\": null,\n" + + " \"repeatedSfixed64\": null,\n" + + " \"repeatedFloat\": null,\n" + + " \"repeatedDouble\": null,\n" + + " \"repeatedBool\": null,\n" + + " \"repeatedString\": null,\n" + + " \"repeatedBytes\": null,\n" + + " \"repeatedNestedMessage\": null,\n" + + " \"repeatedNestedEnum\": null\n" + + "}", builder); + TestAllTypes message = builder.build(); + assertEquals(TestAllTypes.getDefaultInstance(), message); + + // Repeated field elements can also be null. + builder = TestAllTypes.newBuilder(); + mergeFromJson( + "{\n" + + " \"repeatedInt32\": [null, null],\n" + + " \"repeatedInt64\": [null, null],\n" + + " \"repeatedUint32\": [null, null],\n" + + " \"repeatedUint64\": [null, null],\n" + + " \"repeatedSint32\": [null, null],\n" + + " \"repeatedSint64\": [null, null],\n" + + " \"repeatedFixed32\": [null, null],\n" + + " \"repeatedFixed64\": [null, null],\n" + + " \"repeatedSfixed32\": [null, null],\n" + + " \"repeatedSfixed64\": [null, null],\n" + + " \"repeatedFloat\": [null, null],\n" + + " \"repeatedDouble\": [null, null],\n" + + " \"repeatedBool\": [null, null],\n" + + " \"repeatedString\": [null, null],\n" + + " \"repeatedBytes\": [null, null],\n" + + " \"repeatedNestedMessage\": [null, null],\n" + + " \"repeatedNestedEnum\": [null, null]\n" + + "}", builder); + message = builder.build(); + // "null" elements will be parsed to default values. + assertEquals(2, message.getRepeatedInt32Count()); + assertEquals(0, message.getRepeatedInt32(0)); + assertEquals(0, message.getRepeatedInt32(1)); + assertEquals(2, message.getRepeatedInt32Count()); + assertEquals(0, message.getRepeatedInt32(0)); + assertEquals(0, message.getRepeatedInt32(1)); + assertEquals(2, message.getRepeatedInt64Count()); + assertEquals(0, message.getRepeatedInt64(0)); + assertEquals(0, message.getRepeatedInt64(1)); + assertEquals(2, message.getRepeatedUint32Count()); + assertEquals(0, message.getRepeatedUint32(0)); + assertEquals(0, message.getRepeatedUint32(1)); + assertEquals(2, message.getRepeatedUint64Count()); + assertEquals(0, message.getRepeatedUint64(0)); + assertEquals(0, message.getRepeatedUint64(1)); + assertEquals(2, message.getRepeatedSint32Count()); + assertEquals(0, message.getRepeatedSint32(0)); + assertEquals(0, message.getRepeatedSint32(1)); + assertEquals(2, message.getRepeatedSint64Count()); + assertEquals(0, message.getRepeatedSint64(0)); + assertEquals(0, message.getRepeatedSint64(1)); + assertEquals(2, message.getRepeatedFixed32Count()); + assertEquals(0, message.getRepeatedFixed32(0)); + assertEquals(0, message.getRepeatedFixed32(1)); + assertEquals(2, message.getRepeatedFixed64Count()); + assertEquals(0, message.getRepeatedFixed64(0)); + assertEquals(0, message.getRepeatedFixed64(1)); + assertEquals(2, message.getRepeatedSfixed32Count()); + assertEquals(0, message.getRepeatedSfixed32(0)); + assertEquals(0, message.getRepeatedSfixed32(1)); + assertEquals(2, message.getRepeatedSfixed64Count()); + assertEquals(0, message.getRepeatedSfixed64(0)); + assertEquals(0, message.getRepeatedSfixed64(1)); + assertEquals(2, message.getRepeatedFloatCount()); + assertEquals(0f, message.getRepeatedFloat(0)); + assertEquals(0f, message.getRepeatedFloat(1)); + assertEquals(2, message.getRepeatedDoubleCount()); + assertEquals(0.0, message.getRepeatedDouble(0)); + assertEquals(0.0, message.getRepeatedDouble(1)); + assertEquals(2, message.getRepeatedBoolCount()); + assertFalse(message.getRepeatedBool(0)); + assertFalse(message.getRepeatedBool(1)); + assertEquals(2, message.getRepeatedStringCount()); + assertTrue(message.getRepeatedString(0).isEmpty()); + assertTrue(message.getRepeatedString(1).isEmpty()); + assertEquals(2, message.getRepeatedBytesCount()); + assertTrue(message.getRepeatedBytes(0).isEmpty()); + assertTrue(message.getRepeatedBytes(1).isEmpty()); + assertEquals(2, message.getRepeatedNestedMessageCount()); + assertEquals(NestedMessage.getDefaultInstance(), message.getRepeatedNestedMessage(0)); + assertEquals(NestedMessage.getDefaultInstance(), message.getRepeatedNestedMessage(1)); + assertEquals(2, message.getRepeatedNestedEnumCount()); + assertEquals(0, message.getRepeatedNestedEnumValue(0)); + assertEquals(0, message.getRepeatedNestedEnumValue(1)); + } + + public void testMapFields() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + builder.getMutableInt32ToInt32Map().put(1, 10); + builder.getMutableInt64ToInt32Map().put(1234567890123456789L, 10); + builder.getMutableUint32ToInt32Map().put(2, 20); + builder.getMutableUint64ToInt32Map().put(2234567890123456789L, 20); + builder.getMutableSint32ToInt32Map().put(3, 30); + builder.getMutableSint64ToInt32Map().put(3234567890123456789L, 30); + builder.getMutableFixed32ToInt32Map().put(4, 40); + builder.getMutableFixed64ToInt32Map().put(4234567890123456789L, 40); + builder.getMutableSfixed32ToInt32Map().put(5, 50); + builder.getMutableSfixed64ToInt32Map().put(5234567890123456789L, 50); + builder.getMutableBoolToInt32Map().put(false, 6); + builder.getMutableStringToInt32Map().put("Hello", 10); + + builder.getMutableInt32ToInt64Map().put(1, 1234567890123456789L); + builder.getMutableInt32ToUint32Map().put(2, 20); + builder.getMutableInt32ToUint64Map().put(2, 2234567890123456789L); + builder.getMutableInt32ToSint32Map().put(3, 30); + builder.getMutableInt32ToSint64Map().put(3, 3234567890123456789L); + builder.getMutableInt32ToFixed32Map().put(4, 40); + builder.getMutableInt32ToFixed64Map().put(4, 4234567890123456789L); + builder.getMutableInt32ToSfixed32Map().put(5, 50); + builder.getMutableInt32ToSfixed64Map().put(5, 5234567890123456789L); + builder.getMutableInt32ToFloatMap().put(6, 1.5f); + builder.getMutableInt32ToDoubleMap().put(6, 1.25); + builder.getMutableInt32ToBoolMap().put(7, false); + builder.getMutableInt32ToStringMap().put(7, "World"); + builder.getMutableInt32ToBytesMap().put( + 8, ByteString.copyFrom(new byte[]{1, 2, 3})); + builder.getMutableInt32ToMessageMap().put( + 8, NestedMessage.newBuilder().setValue(1234).build()); + builder.getMutableInt32ToEnumMap().put(9, NestedEnum.BAR); + TestMap message = builder.build(); + + assertEquals( + "{\n" + + " \"int32ToInt32Map\": {\n" + + " \"1\": 10\n" + + " },\n" + + " \"int64ToInt32Map\": {\n" + + " \"1234567890123456789\": 10\n" + + " },\n" + + " \"uint32ToInt32Map\": {\n" + + " \"2\": 20\n" + + " },\n" + + " \"uint64ToInt32Map\": {\n" + + " \"2234567890123456789\": 20\n" + + " },\n" + + " \"sint32ToInt32Map\": {\n" + + " \"3\": 30\n" + + " },\n" + + " \"sint64ToInt32Map\": {\n" + + " \"3234567890123456789\": 30\n" + + " },\n" + + " \"fixed32ToInt32Map\": {\n" + + " \"4\": 40\n" + + " },\n" + + " \"fixed64ToInt32Map\": {\n" + + " \"4234567890123456789\": 40\n" + + " },\n" + + " \"sfixed32ToInt32Map\": {\n" + + " \"5\": 50\n" + + " },\n" + + " \"sfixed64ToInt32Map\": {\n" + + " \"5234567890123456789\": 50\n" + + " },\n" + + " \"boolToInt32Map\": {\n" + + " \"false\": 6\n" + + " },\n" + + " \"stringToInt32Map\": {\n" + + " \"Hello\": 10\n" + + " },\n" + + " \"int32ToInt64Map\": {\n" + + " \"1\": \"1234567890123456789\"\n" + + " },\n" + + " \"int32ToUint32Map\": {\n" + + " \"2\": 20\n" + + " },\n" + + " \"int32ToUint64Map\": {\n" + + " \"2\": \"2234567890123456789\"\n" + + " },\n" + + " \"int32ToSint32Map\": {\n" + + " \"3\": 30\n" + + " },\n" + + " \"int32ToSint64Map\": {\n" + + " \"3\": \"3234567890123456789\"\n" + + " },\n" + + " \"int32ToFixed32Map\": {\n" + + " \"4\": 40\n" + + " },\n" + + " \"int32ToFixed64Map\": {\n" + + " \"4\": \"4234567890123456789\"\n" + + " },\n" + + " \"int32ToSfixed32Map\": {\n" + + " \"5\": 50\n" + + " },\n" + + " \"int32ToSfixed64Map\": {\n" + + " \"5\": \"5234567890123456789\"\n" + + " },\n" + + " \"int32ToFloatMap\": {\n" + + " \"6\": 1.5\n" + + " },\n" + + " \"int32ToDoubleMap\": {\n" + + " \"6\": 1.25\n" + + " },\n" + + " \"int32ToBoolMap\": {\n" + + " \"7\": false\n" + + " },\n" + + " \"int32ToStringMap\": {\n" + + " \"7\": \"World\"\n" + + " },\n" + + " \"int32ToBytesMap\": {\n" + + " \"8\": \"AQID\"\n" + + " },\n" + + " \"int32ToMessageMap\": {\n" + + " \"8\": {\n" + + " \"value\": 1234\n" + + " }\n" + + " },\n" + + " \"int32ToEnumMap\": {\n" + + " \"9\": \"BAR\"\n" + + " }\n" + + "}", toJsonString(message)); + assertRoundTripEquals(message); + + // Test multiple entries. + builder = TestMap.newBuilder(); + builder.getMutableInt32ToInt32Map().put(1, 2); + builder.getMutableInt32ToInt32Map().put(3, 4); + message = builder.build(); + + assertEquals( + "{\n" + + " \"int32ToInt32Map\": {\n" + + " \"1\": 2,\n" + + " \"3\": 4\n" + + " }\n" + + "}", toJsonString(message)); + assertRoundTripEquals(message); + } + + public void testMapNullValueIsDefault() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + mergeFromJson( + "{\n" + + " \"int32ToInt32Map\": {\"1\": null},\n" + + " \"int32ToMessageMap\": {\"2\": null}\n" + + "}", builder); + TestMap message = builder.build(); + assertTrue(message.getInt32ToInt32Map().containsKey(1)); + assertEquals(0, message.getInt32ToInt32Map().get(1).intValue()); + assertTrue(message.getInt32ToMessageMap().containsKey(2)); + assertEquals(0, message.getInt32ToMessageMap().get(2).getValue()); + } + + public void testParserAcceptNonQuotedObjectKey() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + mergeFromJson( + "{\n" + + " int32ToInt32Map: {1: 2},\n" + + " stringToInt32Map: {hello: 3}\n" + + "}", builder); + TestMap message = builder.build(); + assertEquals(2, message.getInt32ToInt32Map().get(1).intValue()); + assertEquals(3, message.getStringToInt32Map().get("hello").intValue()); + } + + public void testWrappers() throws Exception { + TestWrappers.Builder builder = TestWrappers.newBuilder(); + builder.getBoolValueBuilder().setValue(false); + builder.getInt32ValueBuilder().setValue(0); + builder.getInt64ValueBuilder().setValue(0); + builder.getUint32ValueBuilder().setValue(0); + builder.getUint64ValueBuilder().setValue(0); + builder.getFloatValueBuilder().setValue(0.0f); + builder.getDoubleValueBuilder().setValue(0.0); + builder.getStringValueBuilder().setValue(""); + builder.getBytesValueBuilder().setValue(ByteString.EMPTY); + TestWrappers message = builder.build(); + + assertEquals( + "{\n" + + " \"int32Value\": 0,\n" + + " \"uint32Value\": 0,\n" + + " \"int64Value\": \"0\",\n" + + " \"uint64Value\": \"0\",\n" + + " \"floatValue\": 0.0,\n" + + " \"doubleValue\": 0.0,\n" + + " \"boolValue\": false,\n" + + " \"stringValue\": \"\",\n" + + " \"bytesValue\": \"\"\n" + + "}", toJsonString(message)); + assertRoundTripEquals(message); + + builder = TestWrappers.newBuilder(); + builder.getBoolValueBuilder().setValue(true); + builder.getInt32ValueBuilder().setValue(1); + builder.getInt64ValueBuilder().setValue(2); + builder.getUint32ValueBuilder().setValue(3); + builder.getUint64ValueBuilder().setValue(4); + builder.getFloatValueBuilder().setValue(5.0f); + builder.getDoubleValueBuilder().setValue(6.0); + builder.getStringValueBuilder().setValue("7"); + builder.getBytesValueBuilder().setValue(ByteString.copyFrom(new byte[]{8})); + message = builder.build(); + + assertEquals( + "{\n" + + " \"int32Value\": 1,\n" + + " \"uint32Value\": 3,\n" + + " \"int64Value\": \"2\",\n" + + " \"uint64Value\": \"4\",\n" + + " \"floatValue\": 5.0,\n" + + " \"doubleValue\": 6.0,\n" + + " \"boolValue\": true,\n" + + " \"stringValue\": \"7\",\n" + + " \"bytesValue\": \"CA==\"\n" + + "}", toJsonString(message)); + assertRoundTripEquals(message); + } + + public void testTimestamp() throws Exception { + TestTimestamp message = TestTimestamp.newBuilder() + .setTimestampValue(TimeUtil.parseTimestamp("1970-01-01T00:00:00Z")) + .build(); + + assertEquals( + "{\n" + + " \"timestampValue\": \"1970-01-01T00:00:00Z\"\n" + + "}", toJsonString(message)); + assertRoundTripEquals(message); + } + + public void testDuration() throws Exception { + TestDuration message = TestDuration.newBuilder() + .setDurationValue(TimeUtil.parseDuration("12345s")) + .build(); + + assertEquals( + "{\n" + + " \"durationValue\": \"12345s\"\n" + + "}", toJsonString(message)); + assertRoundTripEquals(message); + } + + public void testFieldMask() throws Exception { + TestFieldMask message = TestFieldMask.newBuilder() + .setFieldMaskValue(FieldMaskUtil.fromString("foo.bar,baz")) + .build(); + + assertEquals( + "{\n" + + " \"fieldMaskValue\": \"foo.bar,baz\"\n" + + "}", toJsonString(message)); + assertRoundTripEquals(message); + } + + public void testStruct() throws Exception { + // Build a struct with all possible values. + TestStruct.Builder builder = TestStruct.newBuilder(); + Struct.Builder structBuilder = builder.getStructValueBuilder(); + structBuilder.getMutableFields().put( + "null_value", Value.newBuilder().setNullValueValue(0).build()); + structBuilder.getMutableFields().put( + "number_value", Value.newBuilder().setNumberValue(1.25).build()); + structBuilder.getMutableFields().put( + "string_value", Value.newBuilder().setStringValue("hello").build()); + Struct.Builder subStructBuilder = Struct.newBuilder(); + subStructBuilder.getMutableFields().put( + "number_value", Value.newBuilder().setNumberValue(1234).build()); + structBuilder.getMutableFields().put( + "struct_value", Value.newBuilder().setStructValue(subStructBuilder.build()).build()); + ListValue.Builder listBuilder = ListValue.newBuilder(); + listBuilder.addValues(Value.newBuilder().setNumberValue(1.125).build()); + listBuilder.addValues(Value.newBuilder().setNullValueValue(0).build()); + structBuilder.getMutableFields().put( + "list_value", Value.newBuilder().setListValue(listBuilder.build()).build()); + TestStruct message = builder.build(); + + assertEquals( + "{\n" + + " \"structValue\": {\n" + + " \"null_value\": null,\n" + + " \"number_value\": 1.25,\n" + + " \"string_value\": \"hello\",\n" + + " \"struct_value\": {\n" + + " \"number_value\": 1234.0\n" + + " },\n" + + " \"list_value\": [1.125, null]\n" + + " }\n" + + "}", toJsonString(message)); + assertRoundTripEquals(message); + } + + public void testAnyFields() throws Exception { + TestAllTypes content = TestAllTypes.newBuilder().setOptionalInt32(1234).build(); + TestAny message = TestAny.newBuilder().setAnyValue(Any.pack(content)).build(); + + // A TypeRegistry must be provided in order to convert Any types. + try { + toJsonString(message); + fail("Exception is expected."); + } catch (IOException e) { + // Expected. + } + + JsonFormat.TypeRegistry registry = JsonFormat.TypeRegistry.newBuilder() + .add(TestAllTypes.getDescriptor()).build(); + JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry); + + assertEquals( + "{\n" + + " \"anyValue\": {\n" + + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" + + " \"optionalInt32\": 1234\n" + + " }\n" + + "}" , printer.print(message)); + assertRoundTripEquals(message, registry); + + + // Well-known types have a special formatting when embedded in Any. + // + // 1. Any in Any. + Any anyMessage = Any.pack(Any.pack(content)); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.Any\",\n" + + " \"value\": {\n" + + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" + + " \"optionalInt32\": 1234\n" + + " }\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + + // 2. Wrappers in Any. + anyMessage = Any.pack(Int32Value.newBuilder().setValue(12345).build()); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.Int32Value\",\n" + + " \"value\": 12345\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + anyMessage = Any.pack(UInt32Value.newBuilder().setValue(12345).build()); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.UInt32Value\",\n" + + " \"value\": 12345\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + anyMessage = Any.pack(Int64Value.newBuilder().setValue(12345).build()); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.Int64Value\",\n" + + " \"value\": \"12345\"\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + anyMessage = Any.pack(UInt64Value.newBuilder().setValue(12345).build()); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.UInt64Value\",\n" + + " \"value\": \"12345\"\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + anyMessage = Any.pack(FloatValue.newBuilder().setValue(12345).build()); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.FloatValue\",\n" + + " \"value\": 12345.0\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + anyMessage = Any.pack(DoubleValue.newBuilder().setValue(12345).build()); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.DoubleValue\",\n" + + " \"value\": 12345.0\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + anyMessage = Any.pack(BoolValue.newBuilder().setValue(true).build()); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.BoolValue\",\n" + + " \"value\": true\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + anyMessage = Any.pack(StringValue.newBuilder().setValue("Hello").build()); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.StringValue\",\n" + + " \"value\": \"Hello\"\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + anyMessage = Any.pack(BytesValue.newBuilder().setValue( + ByteString.copyFrom(new byte[]{1, 2})).build()); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.BytesValue\",\n" + + " \"value\": \"AQI=\"\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + + // 3. Timestamp in Any. + anyMessage = Any.pack(TimeUtil.parseTimestamp("1969-12-31T23:59:59Z")); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.Timestamp\",\n" + + " \"value\": \"1969-12-31T23:59:59Z\"\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + + // 4. Duration in Any + anyMessage = Any.pack(TimeUtil.parseDuration("12345.10s")); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n" + + " \"value\": \"12345.100s\"\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + + // 5. FieldMask in Any + anyMessage = Any.pack(FieldMaskUtil.fromString("foo.bar,baz")); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.FieldMask\",\n" + + " \"value\": \"foo.bar,baz\"\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + + // 6. Struct in Any + Struct.Builder structBuilder = Struct.newBuilder(); + structBuilder.getMutableFields().put( + "number", Value.newBuilder().setNumberValue(1.125).build()); + anyMessage = Any.pack(structBuilder.build()); + assertEquals( + "{\n" + + " \"@type\": \"type.googleapis.com/google.protobuf.Struct\",\n" + + " \"value\": {\n" + + " \"number\": 1.125\n" + + " }\n" + + "}", printer.print(anyMessage)); + assertRoundTripEquals(anyMessage, registry); + } + + public void testParserMissingTypeUrl() throws Exception { + try { + Any.Builder builder = Any.newBuilder(); + mergeFromJson( + "{\n" + + " \"optionalInt32\": 1234\n" + + "}", builder); + fail("Exception is expected."); + } catch (IOException e) { + // Expected. + } + } + + public void testParserUnexpectedTypeUrl() throws Exception { + try { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + mergeFromJson( + "{\n" + + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" + + " \"optionalInt32\": 12345\n" + + "}", builder); + fail("Exception is expected."); + } catch (IOException e) { + // Expected. + } + } + + public void testParserRejectTrailingComma() throws Exception { + try { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + mergeFromJson( + "{\n" + + " \"optionalInt32\": 12345,\n" + + "}", builder); + fail("Exception is expected."); + } catch (IOException e) { + // Expected. + } + + // TODO(xiaofeng): GSON allows trailing comma in arrays even after I set + // the JsonReader to non-lenient mode. If we want to enforce strict JSON + // compliance, we might want to switch to a different JSON parser or + // implement one by ourselves. + // try { + // TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + // JsonFormat.merge( + // "{\n" + // + " \"repeatedInt32\": [12345,]\n" + // + "}", builder); + // fail("Exception is expected."); + // } catch (IOException e) { + // // Expected. + // } + } + + public void testParserRejectInvalidBase64() throws Exception { + try { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + mergeFromJson( + "{\n" + + " \"optionalBytes\": \"!@#$\"\n" + + "}", builder); + fail("Exception is expected."); + } catch (InvalidProtocolBufferException e) { + // Expected. + } + } + + public void testParserRejectInvalidEnumValue() throws Exception { + try { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + mergeFromJson( + "{\n" + + " \"optionalNestedEnum\": \"XXX\"\n" + + "}", builder); + fail("Exception is expected."); + } catch (InvalidProtocolBufferException e) { + // Expected. + } + } +} diff --git a/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java b/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java new file mode 100644 index 00000000..fe5617e1 --- /dev/null +++ b/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java @@ -0,0 +1,439 @@ +// 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. + +package com.google.protobuf.util; + +import com.google.protobuf.Duration; +import com.google.protobuf.Timestamp; + +import junit.framework.TestCase; + +import org.junit.Assert; + +import java.text.ParseException; + +/** Unit tests for {@link TimeUtil}. */ +public class TimeUtilTest extends TestCase { + public void testTimestampStringFormat() throws Exception { + Timestamp start = TimeUtil.parseTimestamp("0001-01-01T00:00:00Z"); + Timestamp end = TimeUtil.parseTimestamp("9999-12-31T23:59:59.999999999Z"); + assertEquals(TimeUtil.TIMESTAMP_SECONDS_MIN, start.getSeconds()); + assertEquals(0, start.getNanos()); + assertEquals(TimeUtil.TIMESTAMP_SECONDS_MAX, end.getSeconds()); + assertEquals(999999999, end.getNanos()); + assertEquals("0001-01-01T00:00:00Z", TimeUtil.toString(start)); + assertEquals("9999-12-31T23:59:59.999999999Z", TimeUtil.toString(end)); + + Timestamp value = TimeUtil.parseTimestamp("1970-01-01T00:00:00Z"); + assertEquals(0, value.getSeconds()); + assertEquals(0, value.getNanos()); + + // Test negative timestamps. + value = TimeUtil.parseTimestamp("1969-12-31T23:59:59.999Z"); + assertEquals(-1, value.getSeconds()); + // Nano part is in the range of [0, 999999999] for Timestamp. + assertEquals(999000000, value.getNanos()); + + // Test that 3, 6, or 9 digits are used for the fractional part. + value = Timestamp.newBuilder().setNanos(10).build(); + assertEquals("1970-01-01T00:00:00.000000010Z", TimeUtil.toString(value)); + value = Timestamp.newBuilder().setNanos(10000).build(); + assertEquals("1970-01-01T00:00:00.000010Z", TimeUtil.toString(value)); + value = Timestamp.newBuilder().setNanos(10000000).build(); + assertEquals("1970-01-01T00:00:00.010Z", TimeUtil.toString(value)); + + // Test that parsing accepts timezone offsets. + value = TimeUtil.parseTimestamp("1970-01-01T00:00:00.010+08:00"); + assertEquals("1969-12-31T16:00:00.010Z", TimeUtil.toString(value)); + value = TimeUtil.parseTimestamp("1970-01-01T00:00:00.010-08:00"); + assertEquals("1970-01-01T08:00:00.010Z", TimeUtil.toString(value)); + } + + public void testTimetampInvalidFormat() throws Exception { + try { + // Value too small. + Timestamp value = Timestamp.newBuilder() + .setSeconds(TimeUtil.TIMESTAMP_SECONDS_MIN - 1).build(); + TimeUtil.toString(value); + Assert.fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + + try { + // Value too large. + Timestamp value = Timestamp.newBuilder() + .setSeconds(TimeUtil.TIMESTAMP_SECONDS_MAX + 1).build(); + TimeUtil.toString(value); + Assert.fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + + try { + // Invalid nanos value. + Timestamp value = Timestamp.newBuilder().setNanos(-1).build(); + TimeUtil.toString(value); + Assert.fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + + try { + // Invalid nanos value. + Timestamp value = Timestamp.newBuilder().setNanos(1000000000).build(); + TimeUtil.toString(value); + Assert.fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + + try { + // Value to small. + TimeUtil.parseTimestamp("0000-01-01T00:00:00Z"); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + + try { + // Value to large. + TimeUtil.parseTimestamp("10000-01-01T00:00:00Z"); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + + try { + // Missing 'T'. + TimeUtil.parseTimestamp("1970-01-01 00:00:00Z"); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + + try { + // Missing 'Z'. + TimeUtil.parseTimestamp("1970-01-01T00:00:00"); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + + try { + // Invalid offset. + TimeUtil.parseTimestamp("1970-01-01T00:00:00+0000"); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + + try { + // Trailing text. + TimeUtil.parseTimestamp("1970-01-01T00:00:00Z0"); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + + try { + // Invalid nanosecond value. + TimeUtil.parseTimestamp("1970-01-01T00:00:00.ABCZ"); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + } + + public void testDurationStringFormat() throws Exception { + Timestamp start = TimeUtil.parseTimestamp("0001-01-01T00:00:00Z"); + Timestamp end = TimeUtil.parseTimestamp("9999-12-31T23:59:59.999999999Z"); + Duration duration = TimeUtil.distance(start, end); + assertEquals("315537897599.999999999s", TimeUtil.toString(duration)); + duration = TimeUtil.distance(end, start); + assertEquals("-315537897599.999999999s", TimeUtil.toString(duration)); + + // Generated output should contain 3, 6, or 9 fractional digits. + duration = Duration.newBuilder().setSeconds(1).build(); + assertEquals("1s", TimeUtil.toString(duration)); + duration = Duration.newBuilder().setNanos(10000000).build(); + assertEquals("0.010s", TimeUtil.toString(duration)); + duration = Duration.newBuilder().setNanos(10000).build(); + assertEquals("0.000010s", TimeUtil.toString(duration)); + duration = Duration.newBuilder().setNanos(10).build(); + assertEquals("0.000000010s", TimeUtil.toString(duration)); + + // Parsing accepts an fractional digits as long as they fit into nano + // precision. + duration = TimeUtil.parseDuration("0.1s"); + assertEquals(100000000, duration.getNanos()); + duration = TimeUtil.parseDuration("0.0001s"); + assertEquals(100000, duration.getNanos()); + duration = TimeUtil.parseDuration("0.0000001s"); + assertEquals(100, duration.getNanos()); + + // Duration must support range from -315,576,000,000s to +315576000000s + // which includes negative values. + duration = TimeUtil.parseDuration("315576000000.999999999s"); + assertEquals(315576000000L, duration.getSeconds()); + assertEquals(999999999, duration.getNanos()); + duration = TimeUtil.parseDuration("-315576000000.999999999s"); + assertEquals(-315576000000L, duration.getSeconds()); + assertEquals(-999999999, duration.getNanos()); + } + + public void testDurationInvalidFormat() throws Exception { + try { + // Value too small. + Duration value = Duration.newBuilder() + .setSeconds(TimeUtil.DURATION_SECONDS_MIN - 1).build(); + TimeUtil.toString(value); + Assert.fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + + try { + // Value too large. + Duration value = Duration.newBuilder() + .setSeconds(TimeUtil.DURATION_SECONDS_MAX + 1).build(); + TimeUtil.toString(value); + Assert.fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + + try { + // Invalid nanos value. + Duration value = Duration.newBuilder().setSeconds(1).setNanos(-1) + .build(); + TimeUtil.toString(value); + Assert.fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + + try { + // Invalid nanos value. + Duration value = Duration.newBuilder().setSeconds(-1).setNanos(1) + .build(); + TimeUtil.toString(value); + Assert.fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + + try { + // Value too small. + TimeUtil.parseDuration("-315576000001s"); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + + try { + // Value too large. + TimeUtil.parseDuration("315576000001s"); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + + try { + // Empty. + TimeUtil.parseDuration(""); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + + try { + // Missing "s". + TimeUtil.parseDuration("0"); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + + try { + // Invalid trailing data. + TimeUtil.parseDuration("0s0"); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + + try { + // Invalid prefix. + TimeUtil.parseDuration("--1s"); + Assert.fail("Exception is expected."); + } catch (ParseException e) { + // Expected. + } + } + + public void testTimestampConversion() throws Exception { + Timestamp timestamp = + TimeUtil.parseTimestamp("1970-01-01T00:00:01.111111111Z"); + assertEquals(1111111111, TimeUtil.toNanos(timestamp)); + assertEquals(1111111, TimeUtil.toMicros(timestamp)); + assertEquals(1111, TimeUtil.toMillis(timestamp)); + timestamp = TimeUtil.createTimestampFromNanos(1111111111); + assertEquals("1970-01-01T00:00:01.111111111Z", TimeUtil.toString(timestamp)); + timestamp = TimeUtil.createTimestampFromMicros(1111111); + assertEquals("1970-01-01T00:00:01.111111Z", TimeUtil.toString(timestamp)); + timestamp = TimeUtil.createTimestampFromMillis(1111); + assertEquals("1970-01-01T00:00:01.111Z", TimeUtil.toString(timestamp)); + + timestamp = TimeUtil.parseTimestamp("1969-12-31T23:59:59.111111111Z"); + assertEquals(-888888889, TimeUtil.toNanos(timestamp)); + assertEquals(-888889, TimeUtil.toMicros(timestamp)); + assertEquals(-889, TimeUtil.toMillis(timestamp)); + timestamp = TimeUtil.createTimestampFromNanos(-888888889); + assertEquals("1969-12-31T23:59:59.111111111Z", TimeUtil.toString(timestamp)); + timestamp = TimeUtil.createTimestampFromMicros(-888889); + assertEquals("1969-12-31T23:59:59.111111Z", TimeUtil.toString(timestamp)); + timestamp = TimeUtil.createTimestampFromMillis(-889); + assertEquals("1969-12-31T23:59:59.111Z", TimeUtil.toString(timestamp)); + } + + public void testDurationConversion() throws Exception { + Duration duration = TimeUtil.parseDuration("1.111111111s"); + assertEquals(1111111111, TimeUtil.toNanos(duration)); + assertEquals(1111111, TimeUtil.toMicros(duration)); + assertEquals(1111, TimeUtil.toMillis(duration)); + duration = TimeUtil.createDurationFromNanos(1111111111); + assertEquals("1.111111111s", TimeUtil.toString(duration)); + duration = TimeUtil.createDurationFromMicros(1111111); + assertEquals("1.111111s", TimeUtil.toString(duration)); + duration = TimeUtil.createDurationFromMillis(1111); + assertEquals("1.111s", TimeUtil.toString(duration)); + + duration = TimeUtil.parseDuration("-1.111111111s"); + assertEquals(-1111111111, TimeUtil.toNanos(duration)); + assertEquals(-1111111, TimeUtil.toMicros(duration)); + assertEquals(-1111, TimeUtil.toMillis(duration)); + duration = TimeUtil.createDurationFromNanos(-1111111111); + assertEquals("-1.111111111s", TimeUtil.toString(duration)); + duration = TimeUtil.createDurationFromMicros(-1111111); + assertEquals("-1.111111s", TimeUtil.toString(duration)); + duration = TimeUtil.createDurationFromMillis(-1111); + assertEquals("-1.111s", TimeUtil.toString(duration)); + } + + public void testTimeOperations() throws Exception { + Timestamp start = TimeUtil.parseTimestamp("0001-01-01T00:00:00Z"); + Timestamp end = TimeUtil.parseTimestamp("9999-12-31T23:59:59.999999999Z"); + + Duration duration = TimeUtil.distance(start, end); + assertEquals("315537897599.999999999s", TimeUtil.toString(duration)); + Timestamp value = TimeUtil.add(start, duration); + assertEquals(end, value); + value = TimeUtil.subtract(end, duration); + assertEquals(start, value); + + duration = TimeUtil.distance(end, start); + assertEquals("-315537897599.999999999s", TimeUtil.toString(duration)); + value = TimeUtil.add(end, duration); + assertEquals(start, value); + value = TimeUtil.subtract(start, duration); + assertEquals(end, value); + + // Result is larger than Long.MAX_VALUE. + try { + duration = TimeUtil.parseDuration("315537897599.999999999s"); + duration = TimeUtil.multiply(duration, 315537897599.999999999); + Assert.fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + + // Result is lesser than Long.MIN_VALUE. + try { + duration = TimeUtil.parseDuration("315537897599.999999999s"); + duration = TimeUtil.multiply(duration, -315537897599.999999999); + Assert.fail("Exception is expected."); + } catch (IllegalArgumentException e) { + // Expected. + } + + duration = TimeUtil.parseDuration("-1.125s"); + duration = TimeUtil.divide(duration, 2.0); + assertEquals("-0.562500s", TimeUtil.toString(duration)); + duration = TimeUtil.multiply(duration, 2.0); + assertEquals("-1.125s", TimeUtil.toString(duration)); + + duration = TimeUtil.add(duration, duration); + assertEquals("-2.250s", TimeUtil.toString(duration)); + + duration = TimeUtil.subtract(duration, TimeUtil.parseDuration("-1s")); + assertEquals("-1.250s", TimeUtil.toString(duration)); + + // Multiplications (with results larger than Long.MAX_VALUE in nanoseconds). + duration = TimeUtil.parseDuration("0.999999999s"); + assertEquals("315575999684.424s", + TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L))); + duration = TimeUtil.parseDuration("-0.999999999s"); + assertEquals("-315575999684.424s", + TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L))); + assertEquals("315575999684.424s", + TimeUtil.toString(TimeUtil.multiply(duration, -315576000000L))); + + // Divisions (with values larger than Long.MAX_VALUE in nanoseconds). + Duration d1 = TimeUtil.parseDuration("315576000000s"); + Duration d2 = TimeUtil.subtract(d1, TimeUtil.createDurationFromNanos(1)); + assertEquals(1, TimeUtil.divide(d1, d2)); + assertEquals(0, TimeUtil.divide(d2, d1)); + assertEquals("0.000000001s", TimeUtil.toString(TimeUtil.remainder(d1, d2))); + assertEquals("315575999999.999999999s", + TimeUtil.toString(TimeUtil.remainder(d2, d1))); + + // Divisions involving negative values. + // + // (-5) / 2 = -2, remainder = -1 + d1 = TimeUtil.parseDuration("-5s"); + d2 = TimeUtil.parseDuration("2s"); + assertEquals(-2, TimeUtil.divide(d1, d2)); + assertEquals(-2, TimeUtil.divide(d1, 2).getSeconds()); + assertEquals(-1, TimeUtil.remainder(d1, d2).getSeconds()); + // (-5) / (-2) = 2, remainder = -1 + d1 = TimeUtil.parseDuration("-5s"); + d2 = TimeUtil.parseDuration("-2s"); + assertEquals(2, TimeUtil.divide(d1, d2)); + assertEquals(2, TimeUtil.divide(d1, -2).getSeconds()); + assertEquals(-1, TimeUtil.remainder(d1, d2).getSeconds()); + // 5 / (-2) = -2, remainder = 1 + d1 = TimeUtil.parseDuration("5s"); + d2 = TimeUtil.parseDuration("-2s"); + assertEquals(-2, TimeUtil.divide(d1, d2)); + assertEquals(-2, TimeUtil.divide(d1, -2).getSeconds()); + assertEquals(1, TimeUtil.remainder(d1, d2).getSeconds()); + } +} diff --git a/java/util/src/test/java/com/google/protobuf/util/json_test.proto b/java/util/src/test/java/com/google/protobuf/util/json_test.proto new file mode 100644 index 00000000..b2753af6 --- /dev/null +++ b/java/util/src/test/java/com/google/protobuf/util/json_test.proto @@ -0,0 +1,158 @@ +// 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. + +syntax = "proto3"; + +package json_test; + +option java_package = "com.google.protobuf.util"; +option java_outer_classname = "JsonTestProto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/field_mask.proto"; +import "google/protobuf/struct.proto"; + +message TestAllTypes { + enum NestedEnum { + FOO = 0; + BAR = 1; + BAZ = 2; + } + message NestedMessage { + int32 value = 1; + } + + int32 optional_int32 = 1; + int64 optional_int64 = 2; + uint32 optional_uint32 = 3; + uint64 optional_uint64 = 4; + sint32 optional_sint32 = 5; + sint64 optional_sint64 = 6; + fixed32 optional_fixed32 = 7; + fixed64 optional_fixed64 = 8; + sfixed32 optional_sfixed32 = 9; + sfixed64 optional_sfixed64 = 10; + float optional_float = 11; + double optional_double = 12; + bool optional_bool = 13; + string optional_string = 14; + bytes optional_bytes = 15; + NestedMessage optional_nested_message = 18; + NestedEnum optional_nested_enum = 21; + + // 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 NestedEnum repeated_nested_enum = 51; +} + +message TestMap { + // Instead of testing all combinations (too many), we only make sure all + // valid types have been used at least in one field as key and in one + // field as value. + map<int32, int32> int32_to_int32_map = 1; + map<int64, int32> int64_to_int32_map = 2; + map<uint32, int32> uint32_to_int32_map = 3; + map<uint64, int32> uint64_to_int32_map = 4; + map<sint32, int32> sint32_to_int32_map = 5; + map<sint64, int32> sint64_to_int32_map = 6; + map<fixed32, int32> fixed32_to_int32_map = 7; + map<fixed64, int32> fixed64_to_int32_map = 8; + map<sfixed32, int32> sfixed32_to_int32_map = 9; + map<sfixed64, int32> sfixed64_to_int32_map = 10; + map<bool, int32> bool_to_int32_map = 11; + map<string, int32> string_to_int32_map = 12; + + map<int32, int64> int32_to_int64_map = 101; + map<int32, uint32> int32_to_uint32_map = 102; + map<int32, uint64> int32_to_uint64_map = 103; + map<int32, sint32> int32_to_sint32_map = 104; + map<int32, sint64> int32_to_sint64_map = 105; + map<int32, fixed32> int32_to_fixed32_map = 106; + map<int32, fixed64> int32_to_fixed64_map = 107; + map<int32, sfixed32> int32_to_sfixed32_map = 108; + map<int32, sfixed64> int32_to_sfixed64_map = 109; + map<int32, float> int32_to_float_map = 110; + map<int32, double> int32_to_double_map = 111; + map<int32, bool> int32_to_bool_map = 112; + map<int32, string> int32_to_string_map = 113; + map<int32, bytes> int32_to_bytes_map = 114; + map<int32, TestAllTypes.NestedMessage> int32_to_message_map = 115; + map<int32, TestAllTypes.NestedEnum> int32_to_enum_map = 116; +} + +message TestWrappers { + google.protobuf.Int32Value int32_value = 1; + google.protobuf.UInt32Value uint32_value = 2; + google.protobuf.Int64Value int64_value = 3; + google.protobuf.UInt64Value uint64_value = 4; + google.protobuf.FloatValue float_value = 5; + google.protobuf.DoubleValue double_value = 6; + google.protobuf.BoolValue bool_value = 7; + google.protobuf.StringValue string_value = 8; + google.protobuf.BytesValue bytes_value = 9; +} + +message TestTimestamp { + google.protobuf.Timestamp timestamp_value = 1; +} + +message TestDuration { + google.protobuf.Duration duration_value = 1; +} + +message TestFieldMask { + google.protobuf.FieldMask field_mask_value = 1; +} + +message TestStruct { + google.protobuf.Struct struct_value = 1; +} + +message TestAny { + google.protobuf.Any any_value = 1; +} |