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
33using System.Collections.Generic;
34using Google.Protobuf.Collections;
35using Google.Protobuf.TestProtos;
36using NUnit.Framework;
37using Google.Protobuf.WellKnownTypes;
38
39namespace Google.Protobuf
40{
41    public class FieldMaskTreeTest
42    {
43        [Test]
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]
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]
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
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)]
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