Skip to content

Commit 748f57f

Browse files
Update MessageDifferencer to conditionally force comparing additional fields while doing PARTIAL comparison (compare fields which are not repeated, have no presence and are set to their default value).
PiperOrigin-RevId: 527100664
1 parent 28060e4 commit 748f57f

File tree

5 files changed

+278
-0
lines changed

5 files changed

+278
-0
lines changed

src/google/protobuf/util/BUILD.bazel

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ cc_test(
8282
deps = [
8383
":differencer",
8484
":message_differencer_unittest_cc_proto",
85+
":message_differencer_unittest_proto3_cc_proto",
8586
"//src/google/protobuf:cc_test_protos",
8687
"//src/google/protobuf:test_util",
8788
"//src/google/protobuf/testing",
@@ -202,6 +203,7 @@ filegroup(
202203
"json_format.proto",
203204
"json_format_proto3.proto",
204205
"message_differencer_unittest.proto",
206+
"message_differencer_unittest_proto3.proto",
205207
],
206208
visibility = [
207209
"//pkg:__pkg__",
@@ -260,6 +262,19 @@ cc_proto_library(
260262
deps = [":message_differencer_unittest_proto"],
261263
)
262264

265+
proto_library(
266+
name = "message_differencer_unittest_proto3_proto",
267+
testonly = 1,
268+
srcs = ["message_differencer_unittest_proto3.proto"],
269+
strip_import_prefix = "/src",
270+
)
271+
272+
cc_proto_library(
273+
name = "message_differencer_unittest_proto3_cc_proto",
274+
testonly = 1,
275+
deps = [":message_differencer_unittest_proto3_proto"],
276+
)
277+
263278
################################################################################
264279
# Distribution packaging
265280
################################################################################

src/google/protobuf/util/message_differencer.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,10 @@ void MessageDifferencer::set_scope(Scope scope) { scope_ = scope; }
368368

369369
MessageDifferencer::Scope MessageDifferencer::scope() const { return scope_; }
370370

371+
void MessageDifferencer::set_force_compare_no_presence(bool value) {
372+
force_compare_no_presence_ = value;
373+
}
374+
371375
void MessageDifferencer::set_float_comparison(FloatComparison comparison) {
372376
default_field_comparator_.set_float_comparison(
373377
comparison == EXACT ? DefaultFieldComparator::EXACT
@@ -759,6 +763,17 @@ FieldDescriptorArray MessageDifferencer::CombineFields(
759763
} else if (FieldBefore(field2, field1)) {
760764
if (fields2_scope == FULL) {
761765
tmp_message_fields_.push_back(fields2[index2]);
766+
} else if (fields2_scope == PARTIAL && force_compare_no_presence_ &&
767+
!field2->has_presence() && !field2->is_repeated()) {
768+
// In order to make MessageDifferencer play nicely with no-presence
769+
// fields in unit tests, we want to check if the expected proto
770+
// (message1) has some fields which are set to their default value but
771+
// are not set to their default value in message2 (the actual message).
772+
// Those fields will appear in fields2 (since they have non default
773+
// value) but will not appear in fields1 (since they have the default
774+
// value or were never set).
775+
force_compare_no_presence_fields_.insert(fields2[index2]);
776+
tmp_message_fields_.push_back(fields2[index2]);
762777
}
763778
++index2;
764779
} else {
@@ -889,6 +904,10 @@ bool MessageDifferencer::CompareWithFieldsInternal(
889904
specific_field.new_index = -1;
890905
}
891906

907+
specific_field.forced_compare_no_presence_ =
908+
force_compare_no_presence_ &&
909+
force_compare_no_presence_fields_.contains(specific_field.field);
910+
892911
parent_fields->push_back(specific_field);
893912
reporter_->ReportAdded(message1, message2, *parent_fields);
894913
parent_fields->pop_back();
@@ -944,6 +963,10 @@ bool MessageDifferencer::CompareWithFieldsInternal(
944963
specific_field.unpacked_any = unpacked_any;
945964
specific_field.field = field1;
946965
parent_fields->push_back(specific_field);
966+
specific_field.forced_compare_no_presence_ =
967+
force_compare_no_presence_ &&
968+
force_compare_no_presence_fields_.contains(field1);
969+
947970
if (fieldDifferent) {
948971
reporter_->ReportModified(message1, message2, *parent_fields);
949972
isDifferent = true;
@@ -2048,6 +2071,9 @@ void MessageDifferencer::StreamReporter::PrintPath(
20482071
printer_->Print("($name$)", "name", specific_field.field->full_name());
20492072
} else {
20502073
printer_->PrintRaw(specific_field.field->name());
2074+
if (specific_field.forced_compare_no_presence_) {
2075+
printer_->Print(" (added for better PARTIAL comparison)");
2076+
}
20512077
}
20522078

20532079
if (specific_field.field->is_map()) {

src/google/protobuf/util/message_differencer.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ class PROTOBUF_EXPORT MessageDifferencer {
229229
// reporting an addition or deletion.
230230
int unknown_field_index1 = -1;
231231
int unknown_field_index2 = -1;
232+
233+
// Was this field added to the diffing because set_force_compare_no_presence
234+
// was called on the MessageDifferencer object.
235+
bool forced_compare_no_presence_ = false;
232236
};
233237

234238
// Abstract base class from which all MessageDifferencer
@@ -600,6 +604,12 @@ class PROTOBUF_EXPORT MessageDifferencer {
600604
// Returns the current scope used by this differencer.
601605
Scope scope() const;
602606

607+
// Only affects PARTIAL diffing. When set, all non-repeated no-presence fields
608+
// which are set to their default value (which is the same as being unset) in
609+
// message1 but are set to a non-default value in message2 will also be used
610+
// in the comparison.
611+
void set_force_compare_no_presence(bool value);
612+
603613
// DEPRECATED. Pass a DefaultFieldComparator instance instead.
604614
// Sets the type of comparison (as defined in the FloatComparison enumeration
605615
// above) that is used by this differencer when comparing float (and double)
@@ -936,6 +946,7 @@ class PROTOBUF_EXPORT MessageDifferencer {
936946
DefaultFieldComparator default_field_comparator_;
937947
MessageFieldComparison message_field_comparison_;
938948
Scope scope_;
949+
absl::flat_hash_set<const FieldDescriptor*> force_compare_no_presence_fields_;
939950
RepeatedFieldComparison repeated_field_comparison_;
940951

941952
absl::flat_hash_map<const FieldDescriptor*, RepeatedFieldComparison>
@@ -965,6 +976,7 @@ class PROTOBUF_EXPORT MessageDifferencer {
965976
bool report_matches_;
966977
bool report_moves_;
967978
bool report_ignores_;
979+
bool force_compare_no_presence_ = false;
968980

969981
std::string* output_string_;
970982

src/google/protobuf/util/message_differencer_unittest.cc

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@
5858
#include "google/protobuf/unittest.pb.h"
5959
#include "google/protobuf/util/field_comparator.h"
6060
#include "google/protobuf/util/message_differencer_unittest.pb.h"
61+
#include "google/protobuf/util/message_differencer_unittest_proto3.pb.h"
6162
#include "google/protobuf/wire_format.h"
63+
#include "google/protobuf/wire_format_lite.h"
6264

6365

6466
namespace google {
@@ -67,6 +69,15 @@ namespace protobuf {
6769
namespace {
6870

6971

72+
proto3_unittest::TestNoPresenceField MakeTestNoPresenceField() {
73+
proto3_unittest::TestNoPresenceField msg1, msg2;
74+
msg1.set_no_presence_bool(true);
75+
msg2 = msg1;
76+
*msg1.mutable_no_presence_nested() = msg2;
77+
*msg1.add_no_presence_repeated_nested() = msg2;
78+
return msg1;
79+
}
80+
7081
const FieldDescriptor* GetFieldDescriptor(const Message& message,
7182
const std::string& field_name) {
7283
std::vector<std::string> field_path =
@@ -201,6 +212,17 @@ TEST(MessageDifferencerTest, BasicPartialEqualityTest) {
201212
EXPECT_TRUE(differencer.Compare(msg1, msg2));
202213
}
203214

215+
TEST(MessageDifferencerTest, BasicPartialEqualityTestNoPresenceForceCompare) {
216+
util::MessageDifferencer differencer;
217+
differencer.set_scope(util::MessageDifferencer::PARTIAL);
218+
differencer.set_force_compare_no_presence(true);
219+
220+
// Create the testing protos
221+
proto3_unittest::TestNoPresenceField msg1 = MakeTestNoPresenceField();
222+
proto3_unittest::TestNoPresenceField msg2 = MakeTestNoPresenceField();
223+
EXPECT_TRUE(differencer.Compare(msg1, msg2));
224+
}
225+
204226
TEST(MessageDifferencerTest, PartialEqualityTestExtraField) {
205227
// Create the testing protos
206228
unittest::TestAllTypes msg1;
@@ -217,6 +239,165 @@ TEST(MessageDifferencerTest, PartialEqualityTestExtraField) {
217239
EXPECT_TRUE(differencer.Compare(msg1, msg2));
218240
}
219241

242+
TEST(MessageDifferencerTest,
243+
PartialEqualityTestExtraFieldNoPresenceForceCompare) {
244+
util::MessageDifferencer force_compare_differencer;
245+
force_compare_differencer.set_scope(util::MessageDifferencer::PARTIAL);
246+
force_compare_differencer.set_force_compare_no_presence(true);
247+
248+
// This differencer is not setting force_compare_no_presence.
249+
util::MessageDifferencer default_differencer;
250+
default_differencer.set_scope(util::MessageDifferencer::PARTIAL);
251+
default_differencer.set_force_compare_no_presence(false);
252+
253+
254+
// Create the testing protos
255+
proto3_unittest::TestNoPresenceField msg1 = MakeTestNoPresenceField();
256+
proto3_unittest::TestNoPresenceField msg2 = MakeTestNoPresenceField();
257+
258+
// Clearing a no presence field inside a repeated field in a nested message.
259+
msg1.mutable_no_presence_repeated_nested(0)->clear_no_presence_bool();
260+
EXPECT_FALSE(force_compare_differencer.Compare(msg1, msg2));
261+
EXPECT_TRUE(default_differencer.Compare(msg1, msg2));
262+
force_compare_differencer.ReportDifferencesTo(nullptr);
263+
264+
EXPECT_FALSE(force_compare_differencer.Compare(msg2, msg1));
265+
EXPECT_FALSE(default_differencer.Compare(msg2, msg1));
266+
}
267+
268+
TEST(MessageDifferencerTest,
269+
PartialEqualityTestForceCompareWorksForRepeatedField) {
270+
util::MessageDifferencer force_compare_differencer;
271+
force_compare_differencer.set_scope(util::MessageDifferencer::PARTIAL);
272+
force_compare_differencer.set_force_compare_no_presence(true);
273+
274+
// This differencer is not setting force_compare_no_presence.
275+
util::MessageDifferencer default_differencer;
276+
default_differencer.set_scope(util::MessageDifferencer::PARTIAL);
277+
default_differencer.set_force_compare_no_presence(false);
278+
279+
// Repeated fields always have presence, so clearing them would remove them
280+
// from the comparison.
281+
// Create the testing protos
282+
proto3_unittest::TestNoPresenceField msg1 = MakeTestNoPresenceField();
283+
proto3_unittest::TestNoPresenceField msg2 = MakeTestNoPresenceField();
284+
285+
msg1.clear_no_presence_repeated_nested();
286+
EXPECT_TRUE(force_compare_differencer.Compare(msg1, msg2));
287+
EXPECT_TRUE(default_differencer.Compare(msg1, msg2));
288+
289+
EXPECT_FALSE(force_compare_differencer.Compare(msg2, msg1));
290+
EXPECT_FALSE(default_differencer.Compare(msg2, msg1));
291+
}
292+
293+
TEST(MessageDifferencerTest,
294+
PartialEqualityTestForceCompareWorksForRepeatedFieldInstance) {
295+
util::MessageDifferencer force_compare_differencer;
296+
force_compare_differencer.set_scope(util::MessageDifferencer::PARTIAL);
297+
force_compare_differencer.set_force_compare_no_presence(true);
298+
299+
// This differencer is not setting force_compare_no_presence.
300+
util::MessageDifferencer default_differencer;
301+
default_differencer.set_scope(util::MessageDifferencer::PARTIAL);
302+
default_differencer.set_force_compare_no_presence(false);
303+
304+
// Clearing a field inside a repeated field will trigger a failure when
305+
// forcing comparison for no presence fields.
306+
proto3_unittest::TestNoPresenceField msg1 = MakeTestNoPresenceField();
307+
proto3_unittest::TestNoPresenceField msg2 = MakeTestNoPresenceField();
308+
309+
msg1.mutable_no_presence_nested()->clear_no_presence_bool();
310+
EXPECT_FALSE(force_compare_differencer.Compare(msg1, msg2));
311+
EXPECT_TRUE(default_differencer.Compare(msg1, msg2));
312+
313+
EXPECT_FALSE(force_compare_differencer.Compare(msg2, msg1));
314+
EXPECT_FALSE(default_differencer.Compare(msg2, msg1));
315+
}
316+
317+
TEST(MessageDifferencerTest,
318+
PartialEqualityTestForceCompareIsNoOptForNestedMessages) {
319+
util::MessageDifferencer force_compare_differencer;
320+
force_compare_differencer.set_scope(util::MessageDifferencer::PARTIAL);
321+
force_compare_differencer.set_force_compare_no_presence(true);
322+
323+
// This differencer is not setting force_compare_no_presence.
324+
util::MessageDifferencer default_differencer;
325+
default_differencer.set_scope(util::MessageDifferencer::PARTIAL);
326+
default_differencer.set_force_compare_no_presence(false);
327+
328+
// Nested fields always have presence, so clearing them would remove them
329+
// from the comparison.
330+
proto3_unittest::TestNoPresenceField msg1 = MakeTestNoPresenceField();
331+
proto3_unittest::TestNoPresenceField msg2 = MakeTestNoPresenceField();
332+
333+
msg1.clear_no_presence_nested();
334+
EXPECT_TRUE(force_compare_differencer.Compare(msg1, msg2));
335+
EXPECT_TRUE(default_differencer.Compare(msg1, msg2));
336+
337+
EXPECT_FALSE(force_compare_differencer.Compare(msg2, msg1));
338+
EXPECT_FALSE(default_differencer.Compare(msg2, msg1));
339+
340+
// Creating an instance of the nested field will cause the comparison to fail
341+
// since it contains a no presence singualr field.
342+
msg1.mutable_no_presence_nested();
343+
EXPECT_FALSE(force_compare_differencer.Compare(msg1, msg2));
344+
EXPECT_TRUE(default_differencer.Compare(msg1, msg2));
345+
346+
EXPECT_FALSE(force_compare_differencer.Compare(msg2, msg1));
347+
EXPECT_FALSE(default_differencer.Compare(msg2, msg1));
348+
}
349+
350+
TEST(MessageDifferencerTest,
351+
PartialEqualityTestSingularNoPresenceFieldMissing) {
352+
util::MessageDifferencer force_compare_differencer;
353+
force_compare_differencer.set_scope(util::MessageDifferencer::PARTIAL);
354+
force_compare_differencer.set_force_compare_no_presence(true);
355+
356+
// This differencer is not setting force_compare_no_presence.
357+
util::MessageDifferencer default_differencer;
358+
default_differencer.set_scope(util::MessageDifferencer::PARTIAL);
359+
default_differencer.set_force_compare_no_presence(false);
360+
361+
// When clearing a singular no presence field, it will be included in the
362+
// comparison.
363+
proto3_unittest::TestNoPresenceField msg1 = MakeTestNoPresenceField();
364+
proto3_unittest::TestNoPresenceField msg2 = MakeTestNoPresenceField();
365+
366+
msg1.clear_no_presence_bool();
367+
EXPECT_FALSE(force_compare_differencer.Compare(msg1, msg2));
368+
EXPECT_TRUE(default_differencer.Compare(msg1, msg2));
369+
370+
EXPECT_FALSE(force_compare_differencer.Compare(msg2, msg1));
371+
EXPECT_FALSE(default_differencer.Compare(msg2, msg1));
372+
}
373+
374+
TEST(MessageDifferencerTest,
375+
PartialEqualityTestExtraFieldNoPresenceForceCompareReporterAware) {
376+
std::string output;
377+
// Before we can check the output string, we must make sure the
378+
// StreamReporter is destroyed because its destructor will
379+
// flush the stream.
380+
{
381+
io::StringOutputStream output_stream(&output);
382+
util::MessageDifferencer::StreamReporter reporter(&output_stream);
383+
384+
util::MessageDifferencer force_compare_differencer;
385+
force_compare_differencer.set_scope(util::MessageDifferencer::PARTIAL);
386+
force_compare_differencer.set_force_compare_no_presence(true);
387+
force_compare_differencer.ReportDifferencesTo(&reporter);
388+
389+
// Clearing a no presence field inside a repeated field.
390+
proto3_unittest::TestNoPresenceField msg1 = MakeTestNoPresenceField();
391+
proto3_unittest::TestNoPresenceField msg2 = MakeTestNoPresenceField();
392+
393+
msg1.mutable_no_presence_repeated_nested(0)->clear_no_presence_bool();
394+
EXPECT_FALSE(force_compare_differencer.Compare(msg1, msg2));
395+
}
396+
EXPECT_EQ(output,
397+
"added: no_presence_repeated_nested[0].no_presence_bool (added for "
398+
"better PARTIAL comparison): true\n");
399+
}
400+
220401
TEST(MessageDifferencerTest, PartialEqualityTestSkipRequiredField) {
221402
// Create the testing protos
222403
unittest::TestRequired msg1;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Protocol Buffers - Google's data interchange format
2+
// Copyright 2023 Google Inc. All rights reserved.
3+
// https://developers.google.com/protocol-buffers/
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions are
7+
// met:
8+
//
9+
// * Redistributions of source code must retain the above copyright
10+
// notice, this list of conditions and the following disclaimer.
11+
// * Redistributions in binary form must reproduce the above
12+
// copyright notice, this list of conditions and the following disclaimer
13+
// in the documentation and/or other materials provided with the
14+
// distribution.
15+
// * Neither the name of Google Inc. nor the names of its
16+
// contributors may be used to endorse or promote products derived from
17+
// this software without specific prior written permission.
18+
//
19+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
31+
// This file contains messages for testing repeated field comparison
32+
// LINT: ALLOW_GROUPS
33+
34+
syntax = "proto3";
35+
36+
package proto3_unittest;
37+
38+
option optimize_for = SPEED;
39+
40+
message TestNoPresenceField {
41+
bool no_presence_bool = 1;
42+
TestNoPresenceField no_presence_nested = 2;
43+
repeated TestNoPresenceField no_presence_repeated_nested = 3;
44+
}

0 commit comments

Comments
 (0)