• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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