-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix hash code collision for Vector2D and Vector3D #227
base: master
Are you sure you want to change the base?
Changes from all commits
93a01fa
d313086
ddb75f0
448c2d3
0341398
39c7ef0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,7 +23,6 @@ | |
import java.util.Comparator; | ||
import java.util.List; | ||
import java.util.regex.Pattern; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Undo this formatting. |
||
import org.apache.commons.geometry.core.GeometryTestUtils; | ||
import org.apache.commons.geometry.euclidean.EuclideanTestUtils; | ||
import org.apache.commons.numbers.angle.Angle; | ||
|
@@ -1146,6 +1145,65 @@ void testEqualsAndHashCode_signedZeroConsistency() { | |
Assertions.assertEquals(b.hashCode(), d.hashCode()); | ||
} | ||
|
||
@Test | ||
void testHashCodeCollisions_symmetricAboutZero() { | ||
final double any = Math.random(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that this could create sporadic failures. We can formalise this using some random numbers: @ParameterizedTest
@ValueSource(doubles = {1.23, 4.56, 42, Math.PI})
void testHashCodeCollisions_symmetricAboutZero(double any) { |
||
final double neg = -any; | ||
final double pos = +any; | ||
|
||
final Vector3D negX = Vector3D.of(neg, 0.0, 0.0); | ||
final Vector3D posX = Vector3D.of(pos, 0.0, 0.0); | ||
final Vector3D negY = Vector3D.of(0.0, neg, 0.0); | ||
final Vector3D posY = Vector3D.of(0.0, pos, 0.0); | ||
final Vector3D negZ = Vector3D.of(0.0, 0.0, neg); | ||
final Vector3D posZ = Vector3D.of(0.0, 0.0, pos); | ||
|
||
int xNegHash = negX.hashCode(); | ||
int xPosHash = posX.hashCode(); | ||
int yNegHash = negY.hashCode(); | ||
int yPosHash = posY.hashCode(); | ||
int zNegHash = negZ.hashCode(); | ||
int zPosHash = posZ.hashCode(); | ||
|
||
String xMessage = String.format("negXHash: %s, posXHash: %s (expected to be non-equal)\n", xNegHash, xPosHash); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These should be passed a suppliers to Assertions. However the Assertions will already have a statement containing the two values and that they were expected to be not equal. So we can just use: Assertions.assertNotEquals(zNegHash, zPosHash, "+/- z hash"); |
||
String yMessage = String.format("negYHash: %s, posYHash: %s (expected to be non-equal)\n", yNegHash, yPosHash); | ||
String zMessage = String.format("negZHash: %s, posZHash: %s (expected to be non-equal)\n", zNegHash, zPosHash); | ||
|
||
Assertions.assertNotEquals(zNegHash, zPosHash, zMessage); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are these in z, y, x order? Makes more sense to use x, y, z, order. |
||
Assertions.assertNotEquals(yNegHash, yPosHash, yMessage); | ||
Assertions.assertNotEquals(xNegHash, xPosHash, xMessage); | ||
} | ||
|
||
@Test | ||
void testHashCodeCollisions_symmetricAboutArbitraryValue() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as testHashCodeCollisions_symmetricAboutZero. Use a ParameterizedTest and simplify the assertion message. @ParameterizedTest
@CsvSource(
"1.23, 72.68",
// etc
)
void testHashCodeCollisions_symmetricAboutArbitraryValue(double any, double arb) { |
||
final double any = Math.random(); | ||
final double arb = Math.random(); | ||
final double neg = -any; | ||
final double pos = +any; | ||
|
||
final Vector3D negX = Vector3D.of(neg, arb, arb); | ||
final Vector3D posX = Vector3D.of(pos, arb, arb); | ||
final Vector3D negY = Vector3D.of(arb, neg, arb); | ||
final Vector3D posY = Vector3D.of(arb, pos, arb); | ||
final Vector3D negZ = Vector3D.of(arb, arb, neg); | ||
final Vector3D posZ = Vector3D.of(arb, arb, pos); | ||
|
||
int xNegHash = negX.hashCode(); | ||
int xPosHash = posX.hashCode(); | ||
int yNegHash = negY.hashCode(); | ||
int yPosHash = posY.hashCode(); | ||
int zNegHash = negZ.hashCode(); | ||
int zPosHash = posZ.hashCode(); | ||
|
||
String xMessage = String.format("negXHash: %s, posXHash: %s (expected to be non-equal)\n", xNegHash, xPosHash); | ||
String yMessage = String.format("negYHash: %s, posYHash: %s (expected to be non-equal)\n", yNegHash, yPosHash); | ||
String zMessage = String.format("negZHash: %s, posZHash: %s (expected to be non-equal)\n", zNegHash, zPosHash); | ||
|
||
Assertions.assertNotEquals(zNegHash, zPosHash, zMessage); | ||
Assertions.assertNotEquals(yNegHash, yPosHash, yMessage); | ||
Assertions.assertNotEquals(xNegHash, xPosHash, xMessage); | ||
} | ||
|
||
@Test | ||
void testToString() { | ||
// arrange | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -588,7 +588,6 @@ void testAngle() { | |
Assertions.assertEquals(0.004999958333958323, Vector2D.of(20.0, 0.0).angle(Vector2D.of(20.0, 0.1)), EPS); | ||
} | ||
|
||
|
||
@Test | ||
void testAngle_illegalNorm() { | ||
// arrange | ||
|
@@ -912,6 +911,53 @@ void testHashCode() { | |
Assertions.assertEquals(Vector2D.of(0, Double.NaN).hashCode(), Vector2D.of(Double.NaN, 0).hashCode()); | ||
} | ||
|
||
@Test | ||
void testHashCodeCollisions_symmetricAboutZero() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as previous. Use a ParameterizedTest and simplify the assertion message. |
||
final double any = Math.random(); | ||
final double neg = -any; | ||
final double pos = +any; | ||
|
||
final Vector2D negX = Vector2D.of(neg, 0.0); | ||
final Vector2D posX = Vector2D.of(pos, 0.0); | ||
final Vector2D negY = Vector2D.of(0.0, neg); | ||
final Vector2D posY = Vector2D.of(0.0, pos); | ||
|
||
int xNegHash = negX.hashCode(); | ||
int xPosHash = posX.hashCode(); | ||
int yNegHash = negY.hashCode(); | ||
int yPosHash = posY.hashCode(); | ||
|
||
String xMessage = String.format("xNegHash: %s, xPosHash: %s (expected to be non-equal)\n", xNegHash, xPosHash); | ||
String yMessage = String.format("yNegHash: %s, yPosHash: %s (expected to be non-equal)\n", yNegHash, yPosHash); | ||
|
||
Assertions.assertNotEquals(xNegHash, xPosHash, xMessage); | ||
Assertions.assertNotEquals(yNegHash, yPosHash, yMessage); | ||
} | ||
|
||
@Test | ||
void testHashCodeCollisions_symmetricAboutArbitraryValue() { | ||
final double any = Math.random(); | ||
final double arb = Math.random(); | ||
final double neg = -any; | ||
final double pos = +any; | ||
|
||
final Vector2D negX = Vector2D.of(neg, arb); | ||
final Vector2D posX = Vector2D.of(pos, arb); | ||
final Vector2D negY = Vector2D.of(arb, neg); | ||
final Vector2D posY = Vector2D.of(arb, pos); | ||
|
||
int xNegHash = negX.hashCode(); | ||
int xPosHash = posX.hashCode(); | ||
int yNegHash = negY.hashCode(); | ||
int yPosHash = posY.hashCode(); | ||
|
||
String xMessage = String.format("xNegHash: %s, xPosHash: %s (expected to be non-equal)\n", xNegHash, xPosHash); | ||
String yMessage = String.format("yNegHash: %s, yPosHash: %s (expected to be non-equal)\n", yNegHash, yPosHash); | ||
|
||
Assertions.assertNotEquals(xNegHash, xPosHash, xMessage); | ||
Assertions.assertNotEquals(yNegHash, yPosHash, yMessage); | ||
} | ||
|
||
@Test | ||
void testEquals() { | ||
// arrange | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -664,7 +664,7 @@ void testHashCode() { | |
Assertions.assertEquals(hash, a.hashCode()); | ||
|
||
Assertions.assertNotEquals(hash, b.hashCode()); | ||
Assertions.assertNotEquals(hash, c.hashCode()); | ||
Assertions.assertEquals(hash, c.hashCode()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the two circles are the same then this should be tested using Any test that uses not equals on hash codes for objects that are not equal can potentially fail due to random hash collision. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is testing the hash codes are equal (because the objects are equal) then we should test the objects are equal: // Equal objects have equal hash codes
Assertions.assertEquals(a, c);
Assertions.assertEquals(hash, c.hashCode()); |
||
Assertions.assertNotEquals(hash, d.hashCode()); | ||
|
||
Assertions.assertEquals(hash, e.hashCode()); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Undo this formatting.