1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2015 Google Inc. All rights reserved. 4 // 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file or at 7 // https://developers.google.com/open-source/licenses/bsd 8 #endregion 9 10 using Google.Protobuf.Collections; 11 using Google.Protobuf.TestProtos; 12 using NUnit.Framework; 13 using Google.Protobuf.WellKnownTypes; 14 15 namespace Google.Protobuf 16 { 17 public class FieldMaskTreeTest 18 { 19 [Test] AddFieldPath()20 public void AddFieldPath() 21 { 22 FieldMaskTree tree = new FieldMaskTree(); 23 RepeatedField<string> paths = tree.ToFieldMask().Paths; 24 Assert.AreEqual(0, paths.Count); 25 26 tree.AddFieldPath(""); 27 paths = tree.ToFieldMask().Paths; 28 Assert.AreEqual(1, paths.Count); 29 Assert.Contains("", paths); 30 31 // New branch. 32 tree.AddFieldPath("foo"); 33 paths = tree.ToFieldMask().Paths; 34 Assert.AreEqual(2, paths.Count); 35 Assert.Contains("foo", paths); 36 37 // Redundant path. 38 tree.AddFieldPath("foo"); 39 paths = tree.ToFieldMask().Paths; 40 Assert.AreEqual(2, paths.Count); 41 42 // New branch. 43 tree.AddFieldPath("bar.baz"); 44 paths = tree.ToFieldMask().Paths; 45 Assert.AreEqual(3, paths.Count); 46 Assert.Contains("bar.baz", paths); 47 48 // Redundant sub-path. 49 tree.AddFieldPath("foo.bar"); 50 paths = tree.ToFieldMask().Paths; 51 Assert.AreEqual(3, paths.Count); 52 53 // New branch from a non-root node. 54 tree.AddFieldPath("bar.quz"); 55 paths = tree.ToFieldMask().Paths; 56 Assert.AreEqual(4, paths.Count); 57 Assert.Contains("bar.quz", paths); 58 59 // A path that matches several existing sub-paths. 60 tree.AddFieldPath("bar"); 61 paths = tree.ToFieldMask().Paths; 62 Assert.AreEqual(3, paths.Count); 63 Assert.Contains("foo", paths); 64 Assert.Contains("bar", paths); 65 } 66 67 [Test] MergeFromFieldMask()68 public void MergeFromFieldMask() 69 { 70 FieldMaskTree tree = new FieldMaskTree(); 71 tree.MergeFromFieldMask(new FieldMask 72 { 73 Paths = {"foo", "bar.baz", "bar.quz"} 74 }); 75 RepeatedField<string> paths = tree.ToFieldMask().Paths; 76 Assert.AreEqual(3, paths.Count); 77 Assert.Contains("foo", paths); 78 Assert.Contains("bar.baz", paths); 79 Assert.Contains("bar.quz", paths); 80 81 tree.MergeFromFieldMask(new FieldMask 82 { 83 Paths = {"foo.bar", "bar"} 84 }); 85 paths = tree.ToFieldMask().Paths; 86 Assert.AreEqual(2, paths.Count); 87 Assert.Contains("foo", paths); 88 Assert.Contains("bar", paths); 89 } 90 91 [Test] IntersectFieldPath()92 public void IntersectFieldPath() 93 { 94 FieldMaskTree tree = new FieldMaskTree(); 95 FieldMaskTree result = new FieldMaskTree(); 96 tree.MergeFromFieldMask(new FieldMask 97 { 98 Paths = {"foo", "bar.baz", "bar.quz"} 99 }); 100 101 // Empty path. 102 tree.IntersectFieldPath("", result); 103 RepeatedField<string> paths = result.ToFieldMask().Paths; 104 Assert.AreEqual(0, paths.Count); 105 106 // Non-exist path. 107 tree.IntersectFieldPath("quz", result); 108 paths = result.ToFieldMask().Paths; 109 Assert.AreEqual(0, paths.Count); 110 111 // Sub-path of an existing leaf. 112 tree.IntersectFieldPath("foo.bar", result); 113 paths = result.ToFieldMask().Paths; 114 Assert.AreEqual(1, paths.Count); 115 Assert.Contains("foo.bar", paths); 116 117 // Match an existing leaf node. 118 tree.IntersectFieldPath("foo", result); 119 paths = result.ToFieldMask().Paths; 120 Assert.AreEqual(1, paths.Count); 121 Assert.Contains("foo", paths); 122 123 // Non-exist path. 124 tree.IntersectFieldPath("bar.foo", result); 125 paths = result.ToFieldMask().Paths; 126 Assert.AreEqual(1, paths.Count); 127 Assert.Contains("foo", paths); 128 129 // Match a non-leaf node. 130 tree.IntersectFieldPath("bar", result); 131 paths = result.ToFieldMask().Paths; 132 Assert.AreEqual(3, paths.Count); 133 Assert.Contains("foo", paths); 134 Assert.Contains("bar.baz", paths); 135 Assert.Contains("bar.quz", paths); 136 } 137 Merge(FieldMaskTree tree, IMessage source, IMessage destination, FieldMask.MergeOptions options, bool useDynamicMessage)138 private void Merge(FieldMaskTree tree, IMessage source, IMessage destination, FieldMask.MergeOptions options, bool useDynamicMessage) 139 { 140 if (useDynamicMessage) 141 { 142 var newSource = source.Descriptor.Parser.CreateTemplate(); 143 newSource.MergeFrom(source.ToByteString()); 144 145 var newDestination = source.Descriptor.Parser.CreateTemplate(); 146 newDestination.MergeFrom(destination.ToByteString()); 147 148 tree.Merge(newSource, newDestination, options); 149 150 // Clear before merging: 151 foreach (var fieldDescriptor in destination.Descriptor.Fields.InFieldNumberOrder()) 152 { 153 fieldDescriptor.Accessor.Clear(destination); 154 } 155 destination.MergeFrom(newDestination.ToByteString()); 156 } 157 else 158 { 159 tree.Merge(source, destination, options); 160 } 161 } 162 163 [Test] 164 [TestCase(true)] 165 [TestCase(false)] Merge(bool useDynamicMessage)166 public void Merge(bool useDynamicMessage) 167 { 168 TestAllTypes value = new TestAllTypes 169 { 170 SingleInt32 = 1234, 171 SingleNestedMessage = new TestAllTypes.Types.NestedMessage {Bb = 5678}, 172 RepeatedInt32 = {4321}, 173 RepeatedNestedMessage = {new TestAllTypes.Types.NestedMessage {Bb = 8765}} 174 }; 175 176 NestedTestAllTypes source = new NestedTestAllTypes 177 { 178 Payload = value, 179 Child = new NestedTestAllTypes {Payload = value} 180 }; 181 // Now we have a message source with the following structure: 182 // [root] -+- payload -+- single_int32 183 // | +- single_nested_message 184 // | +- repeated_int32 185 // | +- repeated_nested_message 186 // | 187 // +- child --- payload -+- single_int32 188 // +- single_nested_message 189 // +- repeated_int32 190 // +- repeated_nested_message 191 192 FieldMask.MergeOptions options = new FieldMask.MergeOptions(); 193 194 // Test merging each individual field. 195 NestedTestAllTypes destination = new NestedTestAllTypes(); 196 Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"), 197 source, destination, options, useDynamicMessage); 198 NestedTestAllTypes expected = new NestedTestAllTypes 199 { 200 Payload = new TestAllTypes 201 { 202 SingleInt32 = 1234 203 } 204 }; 205 Assert.AreEqual(expected, destination); 206 207 destination = new NestedTestAllTypes(); 208 Merge(new FieldMaskTree().AddFieldPath("payload.single_nested_message"), 209 source, destination, options, useDynamicMessage); 210 expected = new NestedTestAllTypes 211 { 212 Payload = new TestAllTypes 213 { 214 SingleNestedMessage = new TestAllTypes.Types.NestedMessage {Bb = 5678} 215 } 216 }; 217 Assert.AreEqual(expected, destination); 218 219 destination = new NestedTestAllTypes(); 220 Merge(new FieldMaskTree().AddFieldPath("payload.repeated_int32"), 221 source, destination, options, useDynamicMessage); 222 expected = new NestedTestAllTypes 223 { 224 Payload = new TestAllTypes 225 { 226 RepeatedInt32 = {4321} 227 } 228 }; 229 Assert.AreEqual(expected, destination); 230 231 destination = new NestedTestAllTypes(); 232 Merge(new FieldMaskTree().AddFieldPath("payload.repeated_nested_message"), 233 source, destination, options, useDynamicMessage); 234 expected = new NestedTestAllTypes 235 { 236 Payload = new TestAllTypes 237 { 238 RepeatedNestedMessage = {new TestAllTypes.Types.NestedMessage {Bb = 8765}} 239 } 240 }; 241 Assert.AreEqual(expected, destination); 242 243 destination = new NestedTestAllTypes(); 244 Merge( 245 new FieldMaskTree().AddFieldPath("child.payload.single_int32"), 246 source, 247 destination, 248 options, 249 useDynamicMessage); 250 expected = new NestedTestAllTypes 251 { 252 Child = new NestedTestAllTypes 253 { 254 Payload = new TestAllTypes 255 { 256 SingleInt32 = 1234 257 } 258 } 259 }; 260 Assert.AreEqual(expected, destination); 261 262 destination = new NestedTestAllTypes(); 263 Merge( 264 new FieldMaskTree().AddFieldPath("child.payload.single_nested_message"), 265 source, 266 destination, 267 options, 268 useDynamicMessage); 269 expected = new NestedTestAllTypes 270 { 271 Child = new NestedTestAllTypes 272 { 273 Payload = new TestAllTypes 274 { 275 SingleNestedMessage = new TestAllTypes.Types.NestedMessage {Bb = 5678} 276 } 277 } 278 }; 279 Assert.AreEqual(expected, destination); 280 281 destination = new NestedTestAllTypes(); 282 Merge(new FieldMaskTree().AddFieldPath("child.payload.repeated_int32"), 283 source, destination, options, useDynamicMessage); 284 expected = new NestedTestAllTypes 285 { 286 Child = new NestedTestAllTypes 287 { 288 Payload = new TestAllTypes 289 { 290 RepeatedInt32 = {4321} 291 } 292 } 293 }; 294 Assert.AreEqual(expected, destination); 295 296 destination = new NestedTestAllTypes(); 297 Merge(new FieldMaskTree().AddFieldPath("child.payload.repeated_nested_message"), 298 source, destination, options, useDynamicMessage); 299 expected = new NestedTestAllTypes 300 { 301 Child = new NestedTestAllTypes 302 { 303 Payload = new TestAllTypes 304 { 305 RepeatedNestedMessage = {new TestAllTypes.Types.NestedMessage {Bb = 8765}} 306 } 307 } 308 }; 309 Assert.AreEqual(expected, destination); 310 311 destination = new NestedTestAllTypes(); 312 Merge(new FieldMaskTree().AddFieldPath("child").AddFieldPath("payload"), 313 source, destination, options, useDynamicMessage); 314 Assert.AreEqual(source, destination); 315 316 // Test repeated options. 317 destination = new NestedTestAllTypes 318 { 319 Payload = new TestAllTypes 320 { 321 RepeatedInt32 = { 1000 } 322 } 323 }; 324 Merge(new FieldMaskTree().AddFieldPath("payload.repeated_int32"), 325 source, destination, options, useDynamicMessage); 326 // Default behavior is to append repeated fields. 327 Assert.AreEqual(2, destination.Payload.RepeatedInt32.Count); 328 Assert.AreEqual(1000, destination.Payload.RepeatedInt32[0]); 329 Assert.AreEqual(4321, destination.Payload.RepeatedInt32[1]); 330 // Change to replace repeated fields. 331 options.ReplaceRepeatedFields = true; 332 Merge(new FieldMaskTree().AddFieldPath("payload.repeated_int32"), 333 source, destination, options, useDynamicMessage); 334 Assert.AreEqual(1, destination.Payload.RepeatedInt32.Count); 335 Assert.AreEqual(4321, destination.Payload.RepeatedInt32[0]); 336 337 // Test message options. 338 destination = new NestedTestAllTypes 339 { 340 Payload = new TestAllTypes 341 { 342 SingleInt32 = 1000, 343 SingleUint32 = 2000 344 } 345 }; 346 Merge(new FieldMaskTree().AddFieldPath("payload"), 347 source, destination, options, useDynamicMessage); 348 // Default behavior is to merge message fields. 349 Assert.AreEqual(1234, destination.Payload.SingleInt32); 350 Assert.AreEqual(2000, destination.Payload.SingleUint32); 351 352 // Test merging unset message fields. 353 NestedTestAllTypes clearedSource = source.Clone(); 354 clearedSource.Payload = null; 355 destination = new NestedTestAllTypes(); 356 Merge(new FieldMaskTree().AddFieldPath("payload"), 357 clearedSource, destination, options, useDynamicMessage); 358 Assert.IsNull(destination.Payload); 359 360 // Skip a message field if they are unset in both source and target. 361 destination = new NestedTestAllTypes(); 362 Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"), 363 clearedSource, destination, options, useDynamicMessage); 364 Assert.IsNull(destination.Payload); 365 366 // Change to replace message fields. 367 options.ReplaceMessageFields = true; 368 destination = new NestedTestAllTypes 369 { 370 Payload = new TestAllTypes 371 { 372 SingleInt32 = 1000, 373 SingleUint32 = 2000 374 } 375 }; 376 Merge(new FieldMaskTree().AddFieldPath("payload"), 377 source, destination, options, useDynamicMessage); 378 Assert.AreEqual(1234, destination.Payload.SingleInt32); 379 Assert.AreEqual(0, destination.Payload.SingleUint32); 380 381 // Test merging unset message fields. 382 destination = new NestedTestAllTypes 383 { 384 Payload = new TestAllTypes 385 { 386 SingleInt32 = 1000, 387 SingleUint32 = 2000 388 } 389 }; 390 Merge(new FieldMaskTree().AddFieldPath("payload"), 391 clearedSource, destination, options, useDynamicMessage); 392 Assert.IsNull(destination.Payload); 393 394 // Test merging unset primitive fields. 395 destination = source.Clone(); 396 destination.Payload.SingleInt32 = 0; 397 NestedTestAllTypes sourceWithPayloadInt32Unset = destination; 398 destination = source.Clone(); 399 Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"), 400 sourceWithPayloadInt32Unset, destination, options, useDynamicMessage); 401 Assert.AreEqual(0, destination.Payload.SingleInt32); 402 403 // Change to clear unset primitive fields. 404 options.ReplacePrimitiveFields = true; 405 destination = source.Clone(); 406 Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"), 407 sourceWithPayloadInt32Unset, destination, options, useDynamicMessage); 408 Assert.IsNotNull(destination.Payload); 409 410 // Clear unset primitive fields even if source payload is cleared 411 destination = source.Clone(); 412 Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"), 413 clearedSource, destination, options, useDynamicMessage); 414 Assert.AreEqual(0, destination.Payload.SingleInt32); 415 } 416 417 [Test] MergeWrapperFieldsWithNonNullFieldsInSource()418 public void MergeWrapperFieldsWithNonNullFieldsInSource() 419 { 420 // Instantiate a destination with wrapper-based field types. 421 var destination = new TestWellKnownTypes() 422 { 423 StringField = "Hello", 424 Int32Field = 12, 425 Int64Field = 24, 426 BoolField = true, 427 }; 428 429 // Set up a targeted update. 430 var source = new TestWellKnownTypes() 431 { 432 StringField = "Hi", 433 Int64Field = 240 434 }; 435 436 Merge(new FieldMaskTree().AddFieldPath("string_field").AddFieldPath("int64_field"), 437 source, 438 destination, 439 new FieldMask.MergeOptions(), 440 false); 441 442 // Make sure the targeted fields changed. 443 Assert.AreEqual("Hi", destination.StringField); 444 Assert.AreEqual(240, destination.Int64Field); 445 446 // Prove that non-targeted fields stay intact... 447 Assert.AreEqual(12, destination.Int32Field); 448 Assert.IsTrue(destination.BoolField); 449 450 // ...including default values which were not explicitly set in the destination object. 451 Assert.IsNull(destination.FloatField); 452 } 453 454 [Test] 455 [TestCase(false, "Hello", 24)] 456 [TestCase(true, null, null)] MergeWrapperFieldsWithNullFieldsInSource( bool replaceMessageFields, string expectedStringValue, long? expectedInt64Value)457 public void MergeWrapperFieldsWithNullFieldsInSource( 458 bool replaceMessageFields, 459 string expectedStringValue, 460 long? expectedInt64Value) 461 { 462 // Instantiate a destination with wrapper-based field types. 463 var destination = new TestWellKnownTypes() 464 { 465 StringField = "Hello", 466 Int32Field = 12, 467 Int64Field = 24, 468 BoolField = true, 469 }; 470 471 // Set up a targeted update with null valued fields. 472 var source = new TestWellKnownTypes() 473 { 474 StringField = null, 475 Int64Field = null 476 }; 477 478 Merge(new FieldMaskTree().AddFieldPath("string_field").AddFieldPath("int64_field"), 479 source, 480 destination, 481 new FieldMask.MergeOptions() 482 { 483 ReplaceMessageFields = replaceMessageFields 484 }, 485 false); 486 487 // Make sure the targeted fields changed according to our expectations, depending on the value of ReplaceMessageFields. 488 // When ReplaceMessageFields is false, the null values are not applied to the destination, because, although wrapped types 489 // are semantically primitives, FieldMaskTree.Merge still treats them as message types in order to maintain consistency with other Protobuf 490 // libraries such as Java and C++. 491 Assert.AreEqual(expectedStringValue, destination.StringField); 492 Assert.AreEqual(expectedInt64Value, destination.Int64Field); 493 494 // Prove that non-targeted fields stay intact... 495 Assert.AreEqual(12, destination.Int32Field); 496 Assert.IsTrue(destination.BoolField); 497 498 // ...including default values which were not explicitly set in the destination object. 499 Assert.IsNull(destination.FloatField); 500 } 501 } 502 } 503