1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 //     * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 //     * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 package com.google.protobuf;
32 
33 import com.google.protobuf.Descriptors.Descriptor;
34 import com.google.protobuf.Descriptors.EnumDescriptor;
35 import com.google.protobuf.Descriptors.EnumValueDescriptor;
36 import com.google.protobuf.Descriptors.FieldDescriptor;
37 import com.google.protobuf.FieldPresenceTestProto.TestAllTypes;
38 import com.google.protobuf.Proto2UnknownEnumValuesTestProto.Proto2EnumMessage;
39 import com.google.protobuf.Proto2UnknownEnumValuesTestProto.Proto2EnumMessageWithEnumSubset;
40 import com.google.protobuf.Proto2UnknownEnumValuesTestProto.Proto2TestEnum;
41 import com.google.protobuf.Proto2UnknownEnumValuesTestProto.Proto2TestEnumSubset;
42 import com.google.protobuf.TextFormat.ParseException;
43 import junit.framework.TestCase;
44 
45 /**
46  * Unit tests for protos that keep unknown enum values rather than discard them as unknown fields.
47  */
48 public class UnknownEnumValueTest extends TestCase {
49 
testUnknownEnumValues()50   public void testUnknownEnumValues() throws Exception {
51     TestAllTypes.Builder builder = TestAllTypes.newBuilder();
52     builder.setOptionalNestedEnumValue(4321);
53     builder.addRepeatedNestedEnumValue(5432);
54     builder.addPackedNestedEnumValue(6543);
55     TestAllTypes message = builder.build();
56     assertEquals(4321, message.getOptionalNestedEnumValue());
57     assertEquals(5432, message.getRepeatedNestedEnumValue(0));
58     assertEquals(5432, message.getRepeatedNestedEnumValueList().get(0).intValue());
59     assertEquals(6543, message.getPackedNestedEnumValue(0));
60     // Returns UNRECOGNIZED if an enum type is requested.
61     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getOptionalNestedEnum());
62     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getRepeatedNestedEnum(0));
63     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getRepeatedNestedEnumList().get(0));
64     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getPackedNestedEnum(0));
65 
66     // Test serialization and parsing.
67     ByteString data = message.toByteString();
68     message = TestAllTypes.parseFrom(data);
69     assertEquals(4321, message.getOptionalNestedEnumValue());
70     assertEquals(5432, message.getRepeatedNestedEnumValue(0));
71     assertEquals(5432, message.getRepeatedNestedEnumValueList().get(0).intValue());
72     assertEquals(6543, message.getPackedNestedEnumValue(0));
73     // Returns UNRECOGNIZED if an enum type is requested.
74     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getOptionalNestedEnum());
75     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getRepeatedNestedEnum(0));
76     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getRepeatedNestedEnumList().get(0));
77     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, message.getPackedNestedEnum(0));
78 
79     // Test toBuilder().
80     builder = message.toBuilder();
81     assertEquals(4321, builder.getOptionalNestedEnumValue());
82     assertEquals(5432, builder.getRepeatedNestedEnumValue(0));
83     assertEquals(5432, builder.getRepeatedNestedEnumValueList().get(0).intValue());
84     assertEquals(6543, builder.getPackedNestedEnumValue(0));
85     // Returns UNRECOGNIZED if an enum type is requested.
86     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getOptionalNestedEnum());
87     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getRepeatedNestedEnum(0));
88     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getRepeatedNestedEnumList().get(0));
89     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getPackedNestedEnum(0));
90 
91     // Test mergeFrom().
92     builder = TestAllTypes.newBuilder().mergeFrom(message);
93     assertEquals(4321, builder.getOptionalNestedEnumValue());
94     assertEquals(5432, builder.getRepeatedNestedEnumValue(0));
95     assertEquals(5432, builder.getRepeatedNestedEnumValueList().get(0).intValue());
96     assertEquals(6543, builder.getPackedNestedEnumValue(0));
97     // Returns UNRECOGNIZED if an enum type is requested.
98     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getOptionalNestedEnum());
99     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getRepeatedNestedEnum(0));
100     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getRepeatedNestedEnumList().get(0));
101     assertEquals(TestAllTypes.NestedEnum.UNRECOGNIZED, builder.getPackedNestedEnum(0));
102 
103     // Test equals() and hashCode()
104     TestAllTypes sameMessage = builder.build();
105     assertEquals(message, sameMessage);
106     assertEquals(message.hashCode(), sameMessage.hashCode());
107 
108     // Getting the numeric value of UNRECOGNIZED will throw an exception.
109     try {
110       TestAllTypes.NestedEnum.UNRECOGNIZED.getNumber();
111       fail("Exception is expected.");
112     } catch (IllegalArgumentException e) {
113       // Expected.
114     }
115 
116     // Setting an enum field to an UNRECOGNIZED value will throw an exception.
117     try {
118       builder.setOptionalNestedEnum(builder.getOptionalNestedEnum());
119       fail("Exception is expected.");
120     } catch (IllegalArgumentException e) {
121       // Expected.
122     }
123     try {
124       builder.addRepeatedNestedEnum(builder.getOptionalNestedEnum());
125       fail("Exception is expected.");
126     } catch (IllegalArgumentException e) {
127       // Expected.
128     }
129   }
130 
testUnknownEnumValueInReflectionApi()131   public void testUnknownEnumValueInReflectionApi() throws Exception {
132     Descriptor descriptor = TestAllTypes.getDescriptor();
133     FieldDescriptor optionalNestedEnumField = descriptor.findFieldByName("optional_nested_enum");
134     FieldDescriptor repeatedNestedEnumField = descriptor.findFieldByName("repeated_nested_enum");
135     FieldDescriptor packedNestedEnumField = descriptor.findFieldByName("packed_nested_enum");
136     EnumDescriptor enumType = TestAllTypes.NestedEnum.getDescriptor();
137 
138     TestAllTypes.Builder builder = TestAllTypes.newBuilder();
139     builder.setField(optionalNestedEnumField, enumType.findValueByNumberCreatingIfUnknown(4321));
140     builder.addRepeatedField(
141         repeatedNestedEnumField, enumType.findValueByNumberCreatingIfUnknown(5432));
142     builder.addRepeatedField(
143         packedNestedEnumField, enumType.findValueByNumberCreatingIfUnknown(6543));
144     TestAllTypes message = builder.build();
145 
146     // Getters will return unknown enum values as EnumValueDescriptor.
147     EnumValueDescriptor unknown4321 =
148         (EnumValueDescriptor) message.getField(optionalNestedEnumField);
149     EnumValueDescriptor unknown5432 =
150         (EnumValueDescriptor) message.getRepeatedField(repeatedNestedEnumField, 0);
151     EnumValueDescriptor unknown6543 =
152         (EnumValueDescriptor) message.getRepeatedField(packedNestedEnumField, 0);
153     assertEquals(4321, unknown4321.getNumber());
154     assertEquals(5432, unknown5432.getNumber());
155     assertEquals(6543, unknown6543.getNumber());
156 
157     // Unknown EnumValueDescriptor will map to UNRECOGNIZED.
158     assertEquals(
159         TestAllTypes.NestedEnum.UNRECOGNIZED, TestAllTypes.NestedEnum.valueOf(unknown4321));
160     assertEquals(
161         TestAllTypes.NestedEnum.UNRECOGNIZED, TestAllTypes.NestedEnum.valueOf(unknown5432));
162     assertEquals(
163         TestAllTypes.NestedEnum.UNRECOGNIZED, TestAllTypes.NestedEnum.valueOf(unknown6543));
164 
165     // Setters also accept unknown EnumValueDescriptor.
166     builder.setField(optionalNestedEnumField, unknown6543);
167     builder.setRepeatedField(repeatedNestedEnumField, 0, unknown4321);
168     builder.setRepeatedField(packedNestedEnumField, 0, unknown5432);
169     message = builder.build();
170     // Like other descriptors, unknown EnumValueDescriptor can be compared by
171     // object identity.
172     assertSame(message.getField(optionalNestedEnumField), unknown6543);
173     assertSame(message.getRepeatedField(repeatedNestedEnumField, 0), unknown4321);
174     assertSame(message.getRepeatedField(packedNestedEnumField, 0), unknown5432);
175   }
176 
testUnknownEnumValueWithDynamicMessage()177   public void testUnknownEnumValueWithDynamicMessage() throws Exception {
178     Descriptor descriptor = TestAllTypes.getDescriptor();
179     FieldDescriptor optionalNestedEnumField = descriptor.findFieldByName("optional_nested_enum");
180     FieldDescriptor repeatedNestedEnumField = descriptor.findFieldByName("repeated_nested_enum");
181     FieldDescriptor packedNestedEnumField = descriptor.findFieldByName("packed_nested_enum");
182     EnumDescriptor enumType = TestAllTypes.NestedEnum.getDescriptor();
183 
184     Message dynamicMessageDefaultInstance = DynamicMessage.getDefaultInstance(descriptor);
185 
186     Message.Builder builder = dynamicMessageDefaultInstance.newBuilderForType();
187     builder.setField(optionalNestedEnumField, enumType.findValueByNumberCreatingIfUnknown(4321));
188     builder.addRepeatedField(
189         repeatedNestedEnumField, enumType.findValueByNumberCreatingIfUnknown(5432));
190     builder.addRepeatedField(
191         packedNestedEnumField, enumType.findValueByNumberCreatingIfUnknown(6543));
192     Message message = builder.build();
193     assertEquals(
194         4321, ((EnumValueDescriptor) message.getField(optionalNestedEnumField)).getNumber());
195     assertEquals(
196         5432,
197         ((EnumValueDescriptor) message.getRepeatedField(repeatedNestedEnumField, 0)).getNumber());
198     assertEquals(
199         6543,
200         ((EnumValueDescriptor) message.getRepeatedField(packedNestedEnumField, 0)).getNumber());
201 
202     // Test reflection based serialization/parsing implementation.
203     ByteString data = message.toByteString();
204     message = dynamicMessageDefaultInstance.newBuilderForType().mergeFrom(data).build();
205     assertEquals(
206         4321, ((EnumValueDescriptor) message.getField(optionalNestedEnumField)).getNumber());
207     assertEquals(
208         5432,
209         ((EnumValueDescriptor) message.getRepeatedField(repeatedNestedEnumField, 0)).getNumber());
210     assertEquals(
211         6543,
212         ((EnumValueDescriptor) message.getRepeatedField(packedNestedEnumField, 0)).getNumber());
213 
214     // Test reflection based equals()/hashCode().
215     builder = dynamicMessageDefaultInstance.newBuilderForType();
216     builder.setField(optionalNestedEnumField, enumType.findValueByNumberCreatingIfUnknown(4321));
217     builder.addRepeatedField(
218         repeatedNestedEnumField, enumType.findValueByNumberCreatingIfUnknown(5432));
219     builder.addRepeatedField(
220         packedNestedEnumField, enumType.findValueByNumberCreatingIfUnknown(6543));
221     Message sameMessage = builder.build();
222     assertEquals(message, sameMessage);
223     assertEquals(message.hashCode(), sameMessage.hashCode());
224     builder.setField(optionalNestedEnumField, enumType.findValueByNumberCreatingIfUnknown(0));
225     Message differentMessage = builder.build();
226     assertFalse(message.equals(differentMessage));
227   }
228 
testUnknownEnumValuesInTextFormat()229   public void testUnknownEnumValuesInTextFormat() {
230     TestAllTypes.Builder builder = TestAllTypes.newBuilder();
231     builder.setOptionalNestedEnumValue(4321);
232     builder.addRepeatedNestedEnumValue(5432);
233     builder.addPackedNestedEnumValue(6543);
234     TestAllTypes message = builder.build();
235 
236     // We can print a message with unknown enum values.
237     String textData = TextFormat.printer().printToString(message);
238     assertEquals(
239         "optional_nested_enum: UNKNOWN_ENUM_VALUE_NestedEnum_4321\n"
240             + "repeated_nested_enum: UNKNOWN_ENUM_VALUE_NestedEnum_5432\n"
241             + "packed_nested_enum: UNKNOWN_ENUM_VALUE_NestedEnum_6543\n",
242         textData);
243 
244     // Parsing unknown enum values will fail just like parsing other kinds of
245     // unknown fields.
246     try {
247       TextFormat.merge(textData, builder);
248       fail();
249     } catch (ParseException e) {
250       // expected.
251     }
252   }
253 
testUnknownEnumValuesInProto2()254   public void testUnknownEnumValuesInProto2() throws Exception {
255     Proto2EnumMessage.Builder sourceMessage = Proto2EnumMessage.newBuilder();
256     sourceMessage
257         .addRepeatedPackedEnum(Proto2TestEnum.ZERO)
258         .addRepeatedPackedEnum(Proto2TestEnum.TWO) // Unknown in parsed proto
259         .addRepeatedPackedEnum(Proto2TestEnum.ONE);
260 
261     Proto2EnumMessageWithEnumSubset destMessage =
262         Proto2EnumMessageWithEnumSubset.parseFrom(sourceMessage.build().toByteArray());
263 
264     // Known enum values should be preserved.
265     assertEquals(2, destMessage.getRepeatedPackedEnumCount());
266     assertEquals(Proto2TestEnumSubset.TESTENUM_SUBSET_ZERO, destMessage.getRepeatedPackedEnum(0));
267     assertEquals(Proto2TestEnumSubset.TESTENUM_SUBSET_ONE, destMessage.getRepeatedPackedEnum(1));
268 
269     // Unknown enum values should be found in UnknownFieldSet.
270     UnknownFieldSet unknown = destMessage.getUnknownFields();
271     assertEquals(
272         Proto2TestEnum.TWO_VALUE,
273         unknown
274             .getField(Proto2EnumMessageWithEnumSubset.REPEATED_PACKED_ENUM_FIELD_NUMBER)
275             .getVarintList()
276             .get(0)
277             .longValue());
278   }
279 
testUnknownEnumValuesInProto2WithDynamicMessage()280   public void testUnknownEnumValuesInProto2WithDynamicMessage() throws Exception {
281     Descriptor descriptor = Proto2EnumMessageWithEnumSubset.getDescriptor();
282     FieldDescriptor repeatedPackedField = descriptor.findFieldByName("repeated_packed_enum");
283 
284     Proto2EnumMessage.Builder sourceMessage = Proto2EnumMessage.newBuilder();
285     sourceMessage
286         .addRepeatedPackedEnum(Proto2TestEnum.ZERO)
287         .addRepeatedPackedEnum(Proto2TestEnum.TWO) // Unknown in parsed proto
288         .addRepeatedPackedEnum(Proto2TestEnum.ONE);
289 
290     DynamicMessage message =
291         DynamicMessage.parseFrom(
292             Proto2EnumMessageWithEnumSubset.getDescriptor(), sourceMessage.build().toByteArray());
293 
294     // Known enum values should be preserved.
295     assertEquals(2, message.getRepeatedFieldCount(repeatedPackedField));
296     EnumValueDescriptor enumValue0 =
297         (EnumValueDescriptor) message.getRepeatedField(repeatedPackedField, 0);
298     EnumValueDescriptor enumValue1 =
299         (EnumValueDescriptor) message.getRepeatedField(repeatedPackedField, 1);
300 
301     assertEquals(Proto2TestEnumSubset.TESTENUM_SUBSET_ZERO_VALUE, enumValue0.getNumber());
302     assertEquals(Proto2TestEnumSubset.TESTENUM_SUBSET_ONE_VALUE, enumValue1.getNumber());
303 
304     // Unknown enum values should be found in UnknownFieldSet.
305     UnknownFieldSet unknown = message.getUnknownFields();
306     assertEquals(
307         Proto2TestEnum.TWO_VALUE,
308         unknown.getField(repeatedPackedField.getNumber()).getVarintList().get(0).longValue());
309   }
310 }
311