• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 package com.google.protobuf.util;
9 
10 import static com.google.common.truth.Truth.assertThat;
11 
12 import com.google.protobuf.DynamicMessage;
13 import com.google.protobuf.Message;
14 import com.google.protobuf.UninitializedMessageException;
15 import protobuf_unittest.UnittestProto.NestedTestAllTypes;
16 import protobuf_unittest.UnittestProto.TestAllTypes;
17 import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage;
18 import protobuf_unittest.UnittestProto.TestRequired;
19 import protobuf_unittest.UnittestProto.TestRequiredMessage;
20 import org.junit.Test;
21 import org.junit.runner.RunWith;
22 import org.junit.runners.JUnit4;
23 
24 @RunWith(JUnit4.class)
25 public class FieldMaskTreeTest {
26   @Test
testAddFieldPath()27   public void testAddFieldPath() throws Exception {
28     FieldMaskTree tree = new FieldMaskTree();
29     assertThat(tree.toString()).isEmpty();
30     tree.addFieldPath("");
31     assertThat(tree.toString()).isEmpty();
32     // New branch.
33     tree.addFieldPath("foo");
34     assertThat(tree.toString()).isEqualTo("foo");
35     // Redundant path.
36     tree.addFieldPath("foo");
37     assertThat(tree.toString()).isEqualTo("foo");
38     // New branch.
39     tree.addFieldPath("bar.baz");
40     assertThat(tree.toString()).isEqualTo("bar.baz,foo");
41     // Redundant sub-path.
42     tree.addFieldPath("foo.bar");
43     assertThat(tree.toString()).isEqualTo("bar.baz,foo");
44     // New branch from a non-root node.
45     tree.addFieldPath("bar.quz");
46     assertThat(tree.toString()).isEqualTo("bar.baz,bar.quz,foo");
47     // A path that matches several existing sub-paths.
48     tree.addFieldPath("bar");
49     assertThat(tree.toString()).isEqualTo("bar,foo");
50   }
51 
52   @Test
testMergeFromFieldMask()53   public void testMergeFromFieldMask() throws Exception {
54     FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz"));
55     assertThat(tree.toString()).isEqualTo("bar.baz,bar.quz,foo");
56     tree.mergeFromFieldMask(FieldMaskUtil.fromString("foo.bar,bar"));
57     assertThat(tree.toString()).isEqualTo("bar,foo");
58   }
59 
60   @Test
testRemoveFieldPath()61   public void testRemoveFieldPath() throws Exception {
62     String initialTreeString = "bar.baz,bar.quz.bar,foo";
63     FieldMaskTree tree;
64 
65     // Empty path.
66     tree = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
67     tree.removeFieldPath("");
68     assertThat(tree.toString()).isEqualTo(initialTreeString);
69 
70     // Non-exist sub-path of an existing leaf.
71     tree = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
72     tree.removeFieldPath("foo.bar");
73     assertThat(tree.toString()).isEqualTo(initialTreeString);
74 
75     // Non-exist path.
76     tree = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
77     tree.removeFieldPath("bar.foo");
78     assertThat(tree.toString()).isEqualTo(initialTreeString);
79 
80     // Match an existing leaf node -> remove leaf node.
81     tree = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
82     tree.removeFieldPath("foo");
83     assertThat(tree.toString()).isEqualTo("bar.baz,bar.quz.bar");
84 
85     // Match sub-path of an existing leaf node -> recursive removal.
86     tree = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
87     tree.removeFieldPath("bar.quz.bar");
88     assertThat(tree.toString()).isEqualTo("bar.baz,foo");
89 
90     // Match a non-leaf node -> remove all children.
91     tree = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
92     tree.removeFieldPath("bar");
93     assertThat(tree.toString()).isEqualTo("foo");
94   }
95 
96   @Test
testRemoveFromFieldMask()97   public void testRemoveFromFieldMask() throws Exception {
98     FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz"));
99     assertThat(tree.toString()).isEqualTo("bar.baz,bar.quz,foo");
100     tree.removeFromFieldMask(FieldMaskUtil.fromString("foo.bar,bar"));
101     assertThat(tree.toString()).isEqualTo("foo");
102   }
103 
104   @Test
testIntersectFieldPath()105   public void testIntersectFieldPath() throws Exception {
106     FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz"));
107     FieldMaskTree result = new FieldMaskTree();
108     // Empty path.
109     tree.intersectFieldPath("", result);
110     assertThat(result.toString()).isEmpty();
111     // Non-exist path.
112     tree.intersectFieldPath("quz", result);
113     assertThat(result.toString()).isEmpty();
114     // Sub-path of an existing leaf.
115     tree.intersectFieldPath("foo.bar", result);
116     assertThat(result.toString()).isEqualTo("foo.bar");
117     // Match an existing leaf node.
118     tree.intersectFieldPath("foo", result);
119     assertThat(result.toString()).isEqualTo("foo");
120     // Non-exist path.
121     tree.intersectFieldPath("bar.foo", result);
122     assertThat(result.toString()).isEqualTo("foo");
123     // Match a non-leaf node.
124     tree.intersectFieldPath("bar", result);
125     assertThat(result.toString()).isEqualTo("bar.baz,bar.quz,foo");
126   }
127 
128   @Test
testMerge()129   public void testMerge() throws Exception {
130     testMergeImpl(true);
131     testMergeImpl(false);
132     testMergeRequire(false);
133     testMergeRequire(true);
134   }
135 
merge( FieldMaskTree tree, Message source, Message.Builder builder, FieldMaskUtil.MergeOptions options, boolean useDynamicMessage)136   private void merge(
137       FieldMaskTree tree,
138       Message source,
139       Message.Builder builder,
140       FieldMaskUtil.MergeOptions options,
141       boolean useDynamicMessage)
142       throws Exception {
143     if (useDynamicMessage) {
144       Message.Builder newBuilder =
145           DynamicMessage.newBuilder(source.getDescriptorForType())
146               .mergeFrom(builder.buildPartial().toByteArray());
147       tree.merge(
148           DynamicMessage.newBuilder(source.getDescriptorForType())
149               .mergeFrom(source.toByteArray())
150               .build(),
151           newBuilder,
152           options);
153       builder.clear();
154       builder.mergeFrom(newBuilder.buildPartial());
155     } else {
156       tree.merge(source, builder, options);
157     }
158   }
159 
testMergeRequire(boolean useDynamicMessage)160   private void testMergeRequire(boolean useDynamicMessage) throws Exception {
161     TestRequired value = TestRequired.newBuilder().setA(4321).setB(8765).setC(233333).build();
162     TestRequiredMessage source = TestRequiredMessage.newBuilder().setRequiredMessage(value).build();
163 
164     FieldMaskUtil.MergeOptions options = new FieldMaskUtil.MergeOptions();
165     TestRequiredMessage.Builder builder = TestRequiredMessage.newBuilder();
166     merge(
167         new FieldMaskTree().addFieldPath("required_message.a"),
168         source,
169         builder,
170         options,
171         useDynamicMessage);
172     assertThat(builder.hasRequiredMessage()).isTrue();
173     assertThat(builder.getRequiredMessage().hasA()).isTrue();
174     assertThat(builder.getRequiredMessage().hasB()).isFalse();
175     assertThat(builder.getRequiredMessage().hasC()).isFalse();
176     merge(
177         new FieldMaskTree().addFieldPath("required_message.b").addFieldPath("required_message.c"),
178         source,
179         builder,
180         options,
181         useDynamicMessage);
182     try {
183       assertThat(source).isEqualTo(builder.build());
184     } catch (UninitializedMessageException e) {
185       throw new AssertionError("required field isn't set", e);
186     }
187   }
188 
testMergeImpl(boolean useDynamicMessage)189   private void testMergeImpl(boolean useDynamicMessage) throws Exception {
190     TestAllTypes value =
191         TestAllTypes.newBuilder()
192             .setOptionalInt32(1234)
193             .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678))
194             .addRepeatedInt32(4321)
195             .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765))
196             .build();
197     NestedTestAllTypes source =
198         NestedTestAllTypes.newBuilder()
199             .setPayload(value)
200             .setChild(NestedTestAllTypes.newBuilder().setPayload(value))
201             .build();
202     // Now we have a message source with the following structure:
203     //   [root] -+- payload -+- optional_int32
204     //           |           +- optional_nested_message
205     //           |           +- repeated_int32
206     //           |           +- repeated_nested_message
207     //           |
208     //           +- child --- payload -+- optional_int32
209     //                                 +- optional_nested_message
210     //                                 +- repeated_int32
211     //                                 +- repeated_nested_message
212 
213     FieldMaskUtil.MergeOptions options = new FieldMaskUtil.MergeOptions();
214 
215     // Test merging with an empty FieldMask.
216     NestedTestAllTypes.Builder builder = NestedTestAllTypes.newBuilder();
217     builder.getPayloadBuilder().addRepeatedInt32(1000);
218     merge(new FieldMaskTree(), source, builder, options, useDynamicMessage);
219     NestedTestAllTypes.Builder expected = NestedTestAllTypes.newBuilder();
220     expected.getPayloadBuilder().addRepeatedInt32(1000);
221     assertThat(builder.build()).isEqualTo(expected.build());
222 
223     // Test merging each individual field.
224     builder = NestedTestAllTypes.newBuilder();
225     merge(new FieldMaskTree().addFieldPath("payload.optional_int32"),
226         source, builder, options, useDynamicMessage);
227     expected = NestedTestAllTypes.newBuilder();
228     expected.getPayloadBuilder().setOptionalInt32(1234);
229     assertThat(builder.build()).isEqualTo(expected.build());
230 
231     builder = NestedTestAllTypes.newBuilder();
232     merge(new FieldMaskTree().addFieldPath("payload.optional_nested_message"),
233         source, builder, options, useDynamicMessage);
234     expected = NestedTestAllTypes.newBuilder();
235     expected.getPayloadBuilder().setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678));
236     assertThat(builder.build()).isEqualTo(expected.build());
237 
238     builder = NestedTestAllTypes.newBuilder();
239     merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"),
240         source, builder, options, useDynamicMessage);
241     expected = NestedTestAllTypes.newBuilder();
242     expected.getPayloadBuilder().addRepeatedInt32(4321);
243     assertThat(builder.build()).isEqualTo(expected.build());
244 
245     builder = NestedTestAllTypes.newBuilder();
246     merge(new FieldMaskTree().addFieldPath("payload.repeated_nested_message"),
247         source, builder, options, useDynamicMessage);
248     expected = NestedTestAllTypes.newBuilder();
249     expected.getPayloadBuilder().addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765));
250     assertThat(builder.build()).isEqualTo(expected.build());
251 
252     builder = NestedTestAllTypes.newBuilder();
253     merge(
254         new FieldMaskTree().addFieldPath("child.payload.optional_int32"),
255         source,
256         builder,
257         options,
258         useDynamicMessage);
259     expected = NestedTestAllTypes.newBuilder();
260     expected.getChildBuilder().getPayloadBuilder().setOptionalInt32(1234);
261     assertThat(builder.build()).isEqualTo(expected.build());
262 
263     builder = NestedTestAllTypes.newBuilder();
264     merge(
265         new FieldMaskTree().addFieldPath("child.payload.optional_nested_message"),
266         source,
267         builder,
268         options,
269         useDynamicMessage);
270     expected = NestedTestAllTypes.newBuilder();
271     expected
272         .getChildBuilder()
273         .getPayloadBuilder()
274         .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678));
275     assertThat(builder.build()).isEqualTo(expected.build());
276 
277     builder = NestedTestAllTypes.newBuilder();
278     merge(new FieldMaskTree().addFieldPath("child.payload.repeated_int32"),
279         source, builder, options, useDynamicMessage);
280     expected = NestedTestAllTypes.newBuilder();
281     expected.getChildBuilder().getPayloadBuilder().addRepeatedInt32(4321);
282     assertThat(builder.build()).isEqualTo(expected.build());
283 
284     builder = NestedTestAllTypes.newBuilder();
285     merge(new FieldMaskTree().addFieldPath("child.payload.repeated_nested_message"),
286         source, builder, options, useDynamicMessage);
287     expected = NestedTestAllTypes.newBuilder();
288     expected
289         .getChildBuilder()
290         .getPayloadBuilder()
291         .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765));
292     assertThat(builder.build()).isEqualTo(expected.build());
293 
294     // Test merging all fields.
295     builder = NestedTestAllTypes.newBuilder();
296     merge(new FieldMaskTree().addFieldPath("child").addFieldPath("payload"),
297         source, builder, options, useDynamicMessage);
298     assertThat(builder.build()).isEqualTo(source);
299 
300     // Test repeated options.
301     builder = NestedTestAllTypes.newBuilder();
302     builder.getPayloadBuilder().addRepeatedInt32(1000);
303     merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"),
304         source, builder, options, useDynamicMessage);
305     // Default behavior is to append repeated fields.
306     assertThat(builder.getPayload().getRepeatedInt32Count()).isEqualTo(2);
307     assertThat(builder.getPayload().getRepeatedInt32(0)).isEqualTo(1000);
308     assertThat(builder.getPayload().getRepeatedInt32(1)).isEqualTo(4321);
309     // Change to replace repeated fields.
310     options.setReplaceRepeatedFields(true);
311     merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"),
312         source, builder, options, useDynamicMessage);
313     assertThat(builder.getPayload().getRepeatedInt32Count()).isEqualTo(1);
314     assertThat(builder.getPayload().getRepeatedInt32(0)).isEqualTo(4321);
315 
316     // Test message options.
317     builder = NestedTestAllTypes.newBuilder();
318     builder.getPayloadBuilder().setOptionalInt32(1000);
319     builder.getPayloadBuilder().setOptionalUint32(2000);
320     merge(new FieldMaskTree().addFieldPath("payload"),
321         source, builder, options, useDynamicMessage);
322     // Default behavior is to merge message fields.
323     assertThat(builder.getPayload().getOptionalInt32()).isEqualTo(1234);
324     assertThat(builder.getPayload().getOptionalUint32()).isEqualTo(2000);
325 
326     // Test merging unset message fields.
327     NestedTestAllTypes clearedSource = source.toBuilder().clearPayload().build();
328     builder = NestedTestAllTypes.newBuilder();
329     merge(new FieldMaskTree().addFieldPath("payload"),
330         clearedSource, builder, options, useDynamicMessage);
331     assertThat(builder.hasPayload()).isFalse();
332 
333     // Skip a message field if they are unset in both source and target.
334     builder = NestedTestAllTypes.newBuilder();
335     merge(new FieldMaskTree().addFieldPath("payload.optional_int32"),
336         clearedSource, builder, options, useDynamicMessage);
337     assertThat(builder.hasPayload()).isFalse();
338 
339     // Change to replace message fields.
340     options.setReplaceMessageFields(true);
341     builder = NestedTestAllTypes.newBuilder();
342     builder.getPayloadBuilder().setOptionalInt32(1000);
343     builder.getPayloadBuilder().setOptionalUint32(2000);
344     merge(new FieldMaskTree().addFieldPath("payload"),
345         source, builder, options, useDynamicMessage);
346     assertThat(builder.getPayload().getOptionalInt32()).isEqualTo(1234);
347     assertThat(builder.getPayload().getOptionalUint32()).isEqualTo(0);
348 
349     // Test merging unset message fields.
350     builder = NestedTestAllTypes.newBuilder();
351     builder.getPayloadBuilder().setOptionalInt32(1000);
352     builder.getPayloadBuilder().setOptionalUint32(2000);
353     merge(new FieldMaskTree().addFieldPath("payload"),
354         clearedSource, builder, options, useDynamicMessage);
355     assertThat(builder.hasPayload()).isFalse();
356 
357     // Test merging unset primitive fields.
358     builder = source.toBuilder();
359     builder.getPayloadBuilder().clearOptionalInt32();
360     NestedTestAllTypes sourceWithPayloadInt32Unset = builder.build();
361     builder = source.toBuilder();
362     merge(new FieldMaskTree().addFieldPath("payload.optional_int32"),
363         sourceWithPayloadInt32Unset, builder, options, useDynamicMessage);
364     assertThat(builder.getPayload().hasOptionalInt32()).isTrue();
365     assertThat(builder.getPayload().getOptionalInt32()).isEqualTo(0);
366 
367     // Change to clear unset primitive fields.
368     options.setReplacePrimitiveFields(true);
369     builder = source.toBuilder();
370     merge(new FieldMaskTree().addFieldPath("payload.optional_int32"),
371         sourceWithPayloadInt32Unset, builder, options, useDynamicMessage);
372     assertThat(builder.hasPayload()).isTrue();
373     assertThat(builder.getPayload().hasOptionalInt32()).isFalse();
374   }
375 }
376