Skip to content
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

test(geom): add tests for Rectanglei #23

Closed
wants to merge 1 commit into from
Closed

Conversation

skaldarnar
Copy link
Member

  • feat(geom): add getMin/getMax to Rectanglei/f/d
  • test(geom): Overhaul tests for Rectanglei
    • containsPoint
    • containsRectangle
    • intersection
    • (invalid) RectangleFromPoint

The tests also describe (desirable) contracts for the implemenation. Some tests are currently failing!

Copy link

@4Denthusiast 4Denthusiast left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've mostly only reviewed the RectangleiTest changes. Although I think that a half-inclusive interval (and by extension, rectangle and cuboid) is a more useful primitive, making it all inclusive is at least consistent with BlockRegion, which is more important. There should be tests for isValid too (both rectangles that should and shouldn't be valid).

I would otherwise be fine with leaving it without a test, because it should be pretty obvious, but they're actually currently incorrect: the length and area methods could do with tests too. Ideally this would count the number of points in some rectangles and verify that this matches the reported area, rather than just checking the expected value directly. The containsPoint methods could do with tests too, ideally checking the consistency with the other methods (containsRectangle, union, intersection). The correct semantics for the float version of containsPoint is particularly ambiguous. I feel like the area of the rectangle of floats that a Rectanglei represents ought to have an area equal to the number of integer points it contains, but that leaves open the question of in which direction should the set of included float points extend beyond the included integer points. If it were half-inclusive, the answer would be obvious, but in this case, not so much. Extending beyond each edge by 0.5 is probably best, for consistency with the block grid. Extending it by 1 in the +x and +y directions is also possible, but then you need to have it exclude the next integer coordinate, which is a little inconsistent with all the shapes otherwise being inclusive of their edges (this is another advantage of it extending by 0.5 in each direction). Simply not having a Rectanglei.containsPoint(float, float) method would also be a good option, as (given that we're using inclusive boundaries) there is no option that wouldn't be odd or confusing somehow.

As a general point, a lot of these tests are sort of trivial, and it doesn't seem like getting the tests correct is actually easier than implementing the class itself correctly. A testing strategy that seems somewhat more productive would be testing the consistency of the different methods, e.g. testing that the intersection of two rectangles contains a point iff both original rectangles do, if rectangle A contains rectangle B, which contains rectangle or point C, then A contains C too, the number of integer points contained in a Rectanglei is the same as its area, and the union of two rectangles contains both original rectangles. Statements like these are true for all rectangles, so you can just test them for all the rectangles that fit inside a certain area in a loop, and I think they would be more likely than the current tests to actually reveal errors.

* @return <code>true</code> iff this rectangle contains the rectangle; <code>false</code> otherwise
*/
@Override
public boolean containsRectangle(Rectangleic rectangle) {
return rectangle.minX() >= minX && rectangle.maxX() <= maxX &&
rectangle.minY() >= minY && rectangle.maxY() <= maxY;
rectangle.minY() >= minY && rectangle.maxY() <= maxY;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? It seems to have been nicely aligned before. Is it just to make it an integer multiple of a tab, 4 spaces? That doesn't really make sense if there's no particular reason to have 2 tabs here. It should be either 4 spaces (1 tab, the next standard indentation level) or preferably 7 (so that it actually lines up). (This same comment applies in a lot of other places too.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what the IntelliJ auto-formatter does 🤷 Since we have a common configuration committed in all our repos using that should be fine, or we should put some gradlew plugin to ensure consistent formatting.

I did not change anything by hand here.

() -> assertTrue(rect.containsPoint(MAX), "maximum corner is inclusive"),
() -> assertTrue(rect.containsPoint(0, 0)),
() -> assertFalse(rect.containsPoint(-2, -2)),
() -> assertFalse(rect.containsPoint(2, 2))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including a test that fails on only one axis seems like a good idea.

() -> assertTrue(rect.containsRectangle(new Rectanglei(MIN.x(), MIN.y(), 0, 0)),
"minimum corner is inclusive"),
() -> assertTrue(rect.containsRectangle(new Rectanglei(0, 0, MAX.x(), MAX.y())),
"maximum corner is inclusive"),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These would still be true if the corners were exclusive. The tests are correct, it's just the labels that aren't.

return Stream.of(
Arguments.of(new Rectanglei()), // invalid by default
Arguments.of(new Rectanglei(ZERO)), // a "point"
Arguments.of(new Rectanglei(MIN.x(), MIN.y(), MAX.x(), MIN.y())) // a "line"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the rectangles are inclusive on all sides, a rectangle whose min and max are the same has positive area, and should be valid. I also think that a rectangle with 0 area should be valid too (i.e. in this case, new Rectanglei(0, 0, -1, -1)), although that's a little more subjective.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think that a rectangle with 0 area should be valid too

This would mean that there are no invalid rectangles, and we could just remove that property altogether, wouldn't it?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, a rectangle where either of the lengths is strictly less than 0 would still be invalid.

return Stream.of(
Arguments.of(new Rectanglei()), // invalid by default
Arguments.of(new Rectanglei(ZERO)), // a "point"
Arguments.of(new Rectanglei(MIN.x(), MIN.y(), MAX.x(), MIN.y())) // a "line"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of MIN and MAX here is an excessive and unnecessary degree of abstraction.

"minimum corner is inclusive"),
() -> assertTrue(rect.containsRectangle(new Rectanglei(0, 0, MAX.x(), MAX.y())),
"maximum corner is inclusive"),
() -> assertFalse(rect.containsRectangle(new Rectanglei(MIN.x() - 1, MIN.y() - 1, MAX.x() + 1,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, a test that is false by a smaller margin (fewer directions of non-containment) would be a stronger test. There is only 1 obvious implementation for containsRectangle anyway, but if you're going to test it, I think there are better options.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MethodSource("org.terasology.joml.geom.RectangleiTest#invalidRectangles")
void containsRectangleWithInvalid(Rectanglei invalid) {
Rectanglei rect = new Rectanglei(BASE_RECT);
assertFalse(rect.containsRectangle(invalid));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this function actually should return false. Even if we assume that nobody would ever want to have an invalid rectangle, this method returning false is unlikely to alert them to the issue better than just letting them run into whatever issue the invalid rectangle naturally causes, and this seems like a pretty non-obvious result.

Rectanglei bigger = new Rectanglei(MIN.x() - 1, MIN.y() - 1, MAX.x() + 1, MAX.x() + 1);

assertEquals(rect, rect.intersection(bigger, new Rectanglei()),
"intersection when contained in other yields self");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe test this the other way around too?

Rectanglei rect = new Rectanglei(0, 0, 0, 0);
assertFalse(rect.isValid());
@Nested
class RectangleFromPoint {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My earlier point that this rectangle actually should be valid changes all of these tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's basically the point of asking for your review 😉

- containsPoint
- containsRectangle
- intersection
- (invalid) RectangleFromPoint
@skaldarnar
Copy link
Member Author

I'm closing this as I don't know how to move this forward and navigate through all the assumptions and different views we have. Maybe we need to do it in smaller steps, and approach the API design of thiese math types from a different angle 🤷

I have copied over the summary of properties we came up with in #27 if anyone wants to pick this up again.

@skaldarnar skaldarnar closed this Apr 2, 2021
@skaldarnar skaldarnar deleted the test/rectanglei branch April 2, 2021 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants