• 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 // https://developers.google.com/protocol-buffers/
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are
8 // met:
9 //
10 //     * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 //     * Redistributions in binary form must reproduce the above
13 // copyright notice, this list of conditions and the following disclaimer
14 // in the documentation and/or other materials provided with the
15 // distribution.
16 //     * Neither the name of Google Inc. nor the names of its
17 // contributors may be used to endorse or promote products derived from
18 // this software without specific prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #endregion
32 
33 using System.Collections.Generic;
34 using Google.Protobuf.Collections;
35 using Google.Protobuf.TestProtos;
36 using NUnit.Framework;
37 using Google.Protobuf.WellKnownTypes;
38 
39 namespace Google.Protobuf
40 {
41     public class FieldMaskTreeTest
42     {
43         [Test]
AddFieldPath()44         public void AddFieldPath()
45         {
46             FieldMaskTree tree = new FieldMaskTree();
47             RepeatedField<string> paths = tree.ToFieldMask().Paths;
48             Assert.AreEqual(0, paths.Count);
49 
50             tree.AddFieldPath("");
51             paths = tree.ToFieldMask().Paths;
52             Assert.AreEqual(1, paths.Count);
53             Assert.Contains("", paths);
54 
55             // New branch.
56             tree.AddFieldPath("foo");
57             paths = tree.ToFieldMask().Paths;
58             Assert.AreEqual(2, paths.Count);
59             Assert.Contains("foo", paths);
60 
61             // Redundant path.
62             tree.AddFieldPath("foo");
63             paths = tree.ToFieldMask().Paths;
64             Assert.AreEqual(2, paths.Count);
65 
66             // New branch.
67             tree.AddFieldPath("bar.baz");
68             paths = tree.ToFieldMask().Paths;
69             Assert.AreEqual(3, paths.Count);
70             Assert.Contains("bar.baz", paths);
71 
72             // Redundant sub-path.
73             tree.AddFieldPath("foo.bar");
74             paths = tree.ToFieldMask().Paths;
75             Assert.AreEqual(3, paths.Count);
76 
77             // New branch from a non-root node.
78             tree.AddFieldPath("bar.quz");
79             paths = tree.ToFieldMask().Paths;
80             Assert.AreEqual(4, paths.Count);
81             Assert.Contains("bar.quz", paths);
82 
83             // A path that matches several existing sub-paths.
84             tree.AddFieldPath("bar");
85             paths = tree.ToFieldMask().Paths;
86             Assert.AreEqual(3, paths.Count);
87             Assert.Contains("foo", paths);
88             Assert.Contains("bar", paths);
89         }
90 
91         [Test]
MergeFromFieldMask()92         public void MergeFromFieldMask()
93         {
94             FieldMaskTree tree = new FieldMaskTree();
95             tree.MergeFromFieldMask(new FieldMask
96             {
97                 Paths = {"foo", "bar.baz", "bar.quz"}
98             });
99             RepeatedField<string> paths = tree.ToFieldMask().Paths;
100             Assert.AreEqual(3, paths.Count);
101             Assert.Contains("foo", paths);
102             Assert.Contains("bar.baz", paths);
103             Assert.Contains("bar.quz", paths);
104 
105             tree.MergeFromFieldMask(new FieldMask
106             {
107                 Paths = {"foo.bar", "bar"}
108             });
109             paths = tree.ToFieldMask().Paths;
110             Assert.AreEqual(2, paths.Count);
111             Assert.Contains("foo", paths);
112             Assert.Contains("bar", paths);
113         }
114 
115         [Test]
IntersectFieldPath()116         public void IntersectFieldPath()
117         {
118             FieldMaskTree tree = new FieldMaskTree();
119             FieldMaskTree result = new FieldMaskTree();
120             tree.MergeFromFieldMask(new FieldMask
121             {
122                 Paths = {"foo", "bar.baz", "bar.quz"}
123             });
124 
125             // Empty path.
126             tree.IntersectFieldPath("", result);
127             RepeatedField<string> paths = result.ToFieldMask().Paths;
128             Assert.AreEqual(0, paths.Count);
129 
130             // Non-exist path.
131             tree.IntersectFieldPath("quz", result);
132             paths = result.ToFieldMask().Paths;
133             Assert.AreEqual(0, paths.Count);
134 
135             // Sub-path of an existing leaf.
136             tree.IntersectFieldPath("foo.bar", result);
137             paths = result.ToFieldMask().Paths;
138             Assert.AreEqual(1, paths.Count);
139             Assert.Contains("foo.bar", paths);
140 
141             // Match an existing leaf node.
142             tree.IntersectFieldPath("foo", result);
143             paths = result.ToFieldMask().Paths;
144             Assert.AreEqual(1, paths.Count);
145             Assert.Contains("foo", paths);
146 
147             // Non-exist path.
148             tree.IntersectFieldPath("bar.foo", result);
149             paths = result.ToFieldMask().Paths;
150             Assert.AreEqual(1, paths.Count);
151             Assert.Contains("foo", paths);
152 
153             // Match a non-leaf node.
154             tree.IntersectFieldPath("bar", result);
155             paths = result.ToFieldMask().Paths;
156             Assert.AreEqual(3, paths.Count);
157             Assert.Contains("foo", paths);
158             Assert.Contains("bar.baz", paths);
159             Assert.Contains("bar.quz", paths);
160         }
161 
Merge(FieldMaskTree tree, IMessage source, IMessage destination, FieldMask.MergeOptions options, bool useDynamicMessage)162         private void Merge(FieldMaskTree tree, IMessage source, IMessage destination, FieldMask.MergeOptions options, bool useDynamicMessage)
163         {
164             if (useDynamicMessage)
165             {
166                 var newSource = source.Descriptor.Parser.CreateTemplate();
167                 newSource.MergeFrom(source.ToByteString());
168 
169                 var newDestination = source.Descriptor.Parser.CreateTemplate();
170                 newDestination.MergeFrom(destination.ToByteString());
171 
172                 tree.Merge(newSource, newDestination, options);
173 
174                 // Clear before merging:
175                 foreach (var fieldDescriptor in destination.Descriptor.Fields.InFieldNumberOrder())
176                 {
177                     fieldDescriptor.Accessor.Clear(destination);
178                 }
179                 destination.MergeFrom(newDestination.ToByteString());
180             }
181             else
182             {
183                 tree.Merge(source, destination, options);
184             }
185         }
186 
187         [Test]
188         [TestCase(true)]
189         [TestCase(false)]
Merge(bool useDynamicMessage)190         public void Merge(bool useDynamicMessage)
191         {
192             TestAllTypes value = new TestAllTypes
193             {
194                 SingleInt32 = 1234,
195                 SingleNestedMessage = new TestAllTypes.Types.NestedMessage {Bb = 5678},
196                 RepeatedInt32 = {4321},
197                 RepeatedNestedMessage = {new TestAllTypes.Types.NestedMessage {Bb = 8765}}
198             };
199 
200             NestedTestAllTypes source = new NestedTestAllTypes
201             {
202                 Payload = value,
203                 Child = new NestedTestAllTypes {Payload = value}
204             };
205             // Now we have a message source with the following structure:
206             //   [root] -+- payload -+- single_int32
207             //           |           +- single_nested_message
208             //           |           +- repeated_int32
209             //           |           +- repeated_nested_message
210             //           |
211             //           +- child --- payload -+- single_int32
212             //                                 +- single_nested_message
213             //                                 +- repeated_int32
214             //                                 +- repeated_nested_message
215 
216             FieldMask.MergeOptions options = new FieldMask.MergeOptions();
217 
218             // Test merging each individual field.
219             NestedTestAllTypes destination = new NestedTestAllTypes();
220             Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"),
221                 source, destination, options, useDynamicMessage);
222             NestedTestAllTypes expected = new NestedTestAllTypes
223             {
224                 Payload = new TestAllTypes
225                 {
226                     SingleInt32 = 1234
227                 }
228             };
229             Assert.AreEqual(expected, destination);
230 
231             destination = new NestedTestAllTypes();
232             Merge(new FieldMaskTree().AddFieldPath("payload.single_nested_message"),
233                 source, destination, options, useDynamicMessage);
234             expected = new NestedTestAllTypes
235             {
236                 Payload = new TestAllTypes
237                 {
238                     SingleNestedMessage = new TestAllTypes.Types.NestedMessage {Bb = 5678}
239                 }
240             };
241             Assert.AreEqual(expected, destination);
242 
243             destination = new NestedTestAllTypes();
244             Merge(new FieldMaskTree().AddFieldPath("payload.repeated_int32"),
245                 source, destination, options, useDynamicMessage);
246             expected = new NestedTestAllTypes
247             {
248                 Payload = new TestAllTypes
249                 {
250                     RepeatedInt32 = {4321}
251                 }
252             };
253             Assert.AreEqual(expected, destination);
254 
255             destination = new NestedTestAllTypes();
256             Merge(new FieldMaskTree().AddFieldPath("payload.repeated_nested_message"),
257                 source, destination, options, useDynamicMessage);
258             expected = new NestedTestAllTypes
259             {
260                 Payload = new TestAllTypes
261                 {
262                     RepeatedNestedMessage = {new TestAllTypes.Types.NestedMessage {Bb = 8765}}
263                 }
264             };
265             Assert.AreEqual(expected, destination);
266 
267             destination = new NestedTestAllTypes();
268             Merge(
269                 new FieldMaskTree().AddFieldPath("child.payload.single_int32"),
270                 source,
271                 destination,
272                 options,
273                 useDynamicMessage);
274             expected = new NestedTestAllTypes
275             {
276                 Child = new NestedTestAllTypes
277                 {
278                     Payload = new TestAllTypes
279                     {
280                         SingleInt32 = 1234
281                     }
282                 }
283             };
284             Assert.AreEqual(expected, destination);
285 
286             destination = new NestedTestAllTypes();
287             Merge(
288                 new FieldMaskTree().AddFieldPath("child.payload.single_nested_message"),
289                 source,
290                 destination,
291                 options,
292                 useDynamicMessage);
293             expected = new NestedTestAllTypes
294             {
295                 Child = new NestedTestAllTypes
296                 {
297                     Payload = new TestAllTypes
298                     {
299                         SingleNestedMessage = new TestAllTypes.Types.NestedMessage {Bb = 5678}
300                     }
301                 }
302             };
303             Assert.AreEqual(expected, destination);
304 
305             destination = new NestedTestAllTypes();
306             Merge(new FieldMaskTree().AddFieldPath("child.payload.repeated_int32"),
307                 source, destination, options, useDynamicMessage);
308             expected = new NestedTestAllTypes
309             {
310                 Child = new NestedTestAllTypes
311                 {
312                     Payload = new TestAllTypes
313                     {
314                         RepeatedInt32 = {4321}
315                     }
316                 }
317             };
318             Assert.AreEqual(expected, destination);
319 
320             destination = new NestedTestAllTypes();
321             Merge(new FieldMaskTree().AddFieldPath("child.payload.repeated_nested_message"),
322                 source, destination, options, useDynamicMessage);
323             expected = new NestedTestAllTypes
324             {
325                 Child = new NestedTestAllTypes
326                 {
327                     Payload = new TestAllTypes
328                     {
329                         RepeatedNestedMessage = {new TestAllTypes.Types.NestedMessage {Bb = 8765}}
330                     }
331                 }
332             };
333             Assert.AreEqual(expected, destination);
334 
335             destination = new NestedTestAllTypes();
336             Merge(new FieldMaskTree().AddFieldPath("child").AddFieldPath("payload"),
337                 source, destination, options, useDynamicMessage);
338             Assert.AreEqual(source, destination);
339 
340             // Test repeated options.
341             destination = new NestedTestAllTypes
342             {
343                 Payload = new TestAllTypes
344                 {
345                     RepeatedInt32 = { 1000 }
346                 }
347             };
348             Merge(new FieldMaskTree().AddFieldPath("payload.repeated_int32"),
349                     source, destination, options, useDynamicMessage);
350             // Default behavior is to append repeated fields.
351             Assert.AreEqual(2, destination.Payload.RepeatedInt32.Count);
352             Assert.AreEqual(1000, destination.Payload.RepeatedInt32[0]);
353             Assert.AreEqual(4321, destination.Payload.RepeatedInt32[1]);
354             // Change to replace repeated fields.
355             options.ReplaceRepeatedFields = true;
356             Merge(new FieldMaskTree().AddFieldPath("payload.repeated_int32"),
357                 source, destination, options, useDynamicMessage);
358             Assert.AreEqual(1, destination.Payload.RepeatedInt32.Count);
359             Assert.AreEqual(4321, destination.Payload.RepeatedInt32[0]);
360 
361             // Test message options.
362             destination = new NestedTestAllTypes
363             {
364                 Payload = new TestAllTypes
365                 {
366                     SingleInt32 = 1000,
367                     SingleUint32 = 2000
368                 }
369             };
370             Merge(new FieldMaskTree().AddFieldPath("payload"),
371                     source, destination, options, useDynamicMessage);
372             // Default behavior is to merge message fields.
373             Assert.AreEqual(1234, destination.Payload.SingleInt32);
374             Assert.AreEqual(2000, destination.Payload.SingleUint32);
375 
376             // Test merging unset message fields.
377             NestedTestAllTypes clearedSource = source.Clone();
378             clearedSource.Payload = null;
379             destination = new NestedTestAllTypes();
380             Merge(new FieldMaskTree().AddFieldPath("payload"),
381                 clearedSource, destination, options, useDynamicMessage);
382             Assert.IsNull(destination.Payload);
383 
384             // Skip a message field if they are unset in both source and target.
385             destination = new NestedTestAllTypes();
386             Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"),
387                 clearedSource, destination, options, useDynamicMessage);
388             Assert.IsNull(destination.Payload);
389 
390             // Change to replace message fields.
391             options.ReplaceMessageFields = true;
392             destination = new NestedTestAllTypes
393             {
394                 Payload = new TestAllTypes
395                 {
396                     SingleInt32 = 1000,
397                     SingleUint32 = 2000
398                 }
399             };
400             Merge(new FieldMaskTree().AddFieldPath("payload"),
401                     source, destination, options, useDynamicMessage);
402             Assert.AreEqual(1234, destination.Payload.SingleInt32);
403             Assert.AreEqual(0, destination.Payload.SingleUint32);
404 
405             // Test merging unset message fields.
406             destination = new NestedTestAllTypes
407             {
408                 Payload = new TestAllTypes
409                 {
410                     SingleInt32 = 1000,
411                     SingleUint32 = 2000
412                 }
413             };
414             Merge(new FieldMaskTree().AddFieldPath("payload"),
415                     clearedSource, destination, options, useDynamicMessage);
416             Assert.IsNull(destination.Payload);
417 
418             // Test merging unset primitive fields.
419             destination = source.Clone();
420             destination.Payload.SingleInt32 = 0;
421             NestedTestAllTypes sourceWithPayloadInt32Unset = destination;
422             destination = source.Clone();
423             Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"),
424                 sourceWithPayloadInt32Unset, destination, options, useDynamicMessage);
425             Assert.AreEqual(0, destination.Payload.SingleInt32);
426 
427             // Change to clear unset primitive fields.
428             options.ReplacePrimitiveFields = true;
429             destination = source.Clone();
430             Merge(new FieldMaskTree().AddFieldPath("payload.single_int32"),
431                 sourceWithPayloadInt32Unset, destination, options, useDynamicMessage);
432             Assert.IsNotNull(destination.Payload);
433         }
434 
435     }
436 }
437