1919import com .google .protobuf .Any ;
2020import com .google .protobuf .DynamicMessage ;
2121import com .google .protobuf .InvalidProtocolBufferException ;
22+ import com .google .protobuf .Message ;
2223import com .google .protobuf .Struct ;
24+ import com .google .protobuf .UnknownFieldSet ;
2325import com .google .protobuf .Value ;
26+ import com .google .testing .junit .testparameterinjector .TestParameter ;
27+ import com .google .testing .junit .testparameterinjector .TestParameterInjector ;
2428import dev .cel .expr .conformance .proto3 .TestAllTypes ;
2529import dev .cel .expr .conformance .proto3 .TestAllTypes .NestedEnum ;
2630import dev .cel .expr .conformance .proto3 .TestAllTypes .NestedMessage ;
2731import org .junit .Before ;
2832import org .junit .Test ;
2933import org .junit .runner .RunWith ;
30- import org .junit .runners .JUnit4 ;
3134
32- @ RunWith (JUnit4 .class )
35+ @ RunWith (TestParameterInjector .class )
3336public final class ProtoEqualityTest {
3437
3538 private DynamicProto dynamicProto ;
@@ -59,13 +62,22 @@ public void notEqualsFloatFields() {
5962 .isFalse ();
6063 }
6164
65+ @ Test
66+ public void notEqualsNegativeZeroFields_proto3ImplicitPresence () {
67+ // Proto3 implicit presence treats -0.0 as wire-present and +0.0 as unset, matching C++/Go.
68+ TestAllTypes negativeZero = TestAllTypes .newBuilder ().setSingleDouble (-0.0 ).build ();
69+ TestAllTypes positiveZero = TestAllTypes .newBuilder ().setSingleDouble (0.0 ).build ();
70+
71+ assertThat (protoEquality .equals (negativeZero , positiveZero )).isFalse ();
72+ }
73+
6274 @ Test
6375 public void notEqualsNaNFields () {
64- assertThat (
65- protoEquality . equals (
66- TestAllTypes . newBuilder (). setSingleDouble ( Double . NaN ). build (),
67- TestAllTypes . newBuilder (). setSingleDouble ( Double . NaN ). build ()))
68- .isFalse ();
76+ TestAllTypes nanDouble = TestAllTypes . newBuilder (). setSingleDouble ( Double . NaN ). build ();
77+ TestAllTypes nanFloat = TestAllTypes . newBuilder (). setSingleFloat ( Float . NaN ). build ();
78+
79+ assertThat ( protoEquality . equals ( nanDouble , nanDouble )). isFalse ();
80+ assertThat ( protoEquality . equals ( nanFloat , nanFloat )) .isFalse ();
6981 }
7082
7183 @ Test
@@ -240,6 +252,8 @@ public void equalsMessageAnyFields() {
240252 Any packedStruct = Any .pack (struct );
241253 Any packedStruct2 = Any .pack (struct );
242254 assertThat (protoEquality .equals (packedStruct , packedStruct2 )).isTrue ();
255+ assertThat (protoEquality .hashCode (packedStruct ))
256+ .isEqualTo (protoEquality .hashCode (packedStruct2 ));
243257 }
244258
245259 @ Test
@@ -253,6 +267,8 @@ public void equalsMessageUnknownAnyFields() {
253267 Any packedStruct2 = Any .pack (struct );
254268 packedStruct = packedStruct .toBuilder ().setTypeUrl ("type.googleapis.com/Unknown" ).build ();
255269 assertThat (protoEquality .equals (packedStruct , packedStruct2 )).isTrue ();
270+ assertThat (protoEquality .hashCode (packedStruct ))
271+ .isEqualTo (protoEquality .hashCode (packedStruct2 ));
256272 }
257273
258274 @ Test
@@ -275,5 +291,88 @@ public void equalsMessageDynamicAnyFields() throws InvalidProtocolBufferExceptio
275291 doublePackedStruct .getValue (),
276292 DefaultDescriptorPool .INSTANCE .getExtensionRegistry ());
277293 assertThat (protoEquality .equals (dynAny , dynAny2 )).isTrue ();
294+ assertThat (protoEquality .hashCode (dynAny )).isEqualTo (protoEquality .hashCode (dynAny2 ));
295+ }
296+
297+ @ SuppressWarnings ("ImmutableEnumChecker" ) // Test only
298+ private enum EqualMessagePairTestCase {
299+ REPEATED_SIGNED_ZERO (
300+ TestAllTypes .newBuilder ().addRepeatedDouble (-0.0 ).addRepeatedFloat (-0.0f ).build (),
301+ TestAllTypes .newBuilder ().addRepeatedDouble (0.0 ).addRepeatedFloat (0.0f ).build ()),
302+ MAP_KEY_ORDER (
303+ TestAllTypes .newBuilder ()
304+ .putMapStringString ("a" , "1" )
305+ .putMapStringString ("b" , "10" )
306+ .build (),
307+ TestAllTypes .newBuilder ()
308+ .putMapStringString ("b" , "10" )
309+ .putMapStringString ("a" , "1" )
310+ .build ()),
311+ SINGLE_VALUE_SIGNED_ZERO (
312+ TestAllTypes .newBuilder ().setSingleValue (Value .newBuilder ().setNumberValue (-0.0 )).build (),
313+ TestAllTypes .newBuilder ().setSingleValue (Value .newBuilder ().setNumberValue (0.0 )).build ()),
314+ MAP_DOUBLE_VALUE_SIGNED_ZERO (
315+ TestAllTypes .newBuilder ().putMapInt64Double (1L , -0.0 ).build (),
316+ TestAllTypes .newBuilder ().putMapInt64Double (1L , 0.0 ).build ());
317+
318+ private final Message x ;
319+ private final Message y ;
320+
321+ EqualMessagePairTestCase (Message x , Message y ) {
322+ this .x = x ;
323+ this .y = y ;
324+ }
325+ }
326+
327+ @ SuppressWarnings ("ImmutableEnumChecker" ) // Test only
328+ private enum DistinctMessagePairTestCase {
329+ DIFFERENT_UNKNOWN_FIELDS (
330+ TestAllTypes .getDefaultInstance (),
331+ TestAllTypes .newBuilder ()
332+ .setUnknownFields (
333+ UnknownFieldSet .newBuilder ()
334+ .addField (999 , UnknownFieldSet .Field .newBuilder ().addVarint (1L ).build ())
335+ .build ())
336+ .build ()),
337+ DIFFERENT_SCALAR_DOUBLES (
338+ TestAllTypes .newBuilder ().setSingleDouble (1.0 ).build (),
339+ TestAllTypes .newBuilder ().setSingleDouble (2.0 ).build ()),
340+ DIFFERENT_SCALAR_INTS (
341+ TestAllTypes .newBuilder ().setSingleInt32 (1 ).build (),
342+ TestAllTypes .newBuilder ().setSingleInt32 (2 ).build ()),
343+ DIFFERENT_REPEATED_DOUBLES (
344+ TestAllTypes .newBuilder ().addRepeatedDouble (1.0 ).build (),
345+ TestAllTypes .newBuilder ().addRepeatedDouble (2.0 ).build ()),
346+ DIFFERENT_MAP_VALUES (
347+ TestAllTypes .newBuilder ().putMapStringString ("a" , "1" ).build (),
348+ TestAllTypes .newBuilder ().putMapStringString ("a" , "2" ).build ()),
349+ DIFFERENT_NESTED_MESSAGES (
350+ TestAllTypes .newBuilder ()
351+ .setSingleNestedMessage (NestedMessage .newBuilder ().setBb (1 ))
352+ .build (),
353+ TestAllTypes .newBuilder ()
354+ .setSingleNestedMessage (NestedMessage .newBuilder ().setBb (2 ))
355+ .build ());
356+
357+ private final Message x ;
358+ private final Message y ;
359+
360+ DistinctMessagePairTestCase (Message x , Message y ) {
361+ this .x = x ;
362+ this .y = y ;
363+ }
364+ }
365+
366+ @ Test
367+ public void hashCode_equalMessagesHaveEqualHash (
368+ @ TestParameter EqualMessagePairTestCase testCase ) {
369+ assertThat (protoEquality .equals (testCase .x , testCase .y )).isTrue ();
370+ assertThat (protoEquality .hashCode (testCase .x )).isEqualTo (protoEquality .hashCode (testCase .y ));
371+ }
372+
373+ @ Test
374+ public void hashCode_distinctMessagesHaveDifferentHashes (
375+ @ TestParameter DistinctMessagePairTestCase testCase ) {
376+ assertThat (protoEquality .hashCode (testCase .x )).isNotEqualTo (protoEquality .hashCode (testCase .y ));
278377 }
279378}
0 commit comments