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