It asked whether either rectangle enclosed one of the other's four corners -- eight akgl_collide_point_rectangle calls, stopping at the first hit. That is a different question from "do these overlap", and it has the wrong answer for one arrangement: a tall thin rectangle crossing a short wide one overlaps in a plus sign with no corner of either inside the other, and all eight tests said no. A long thin platform crossing a tall thin character is exactly that shape, so this is a shape a 2D game produces, not a curiosity. util.h carried an @note describing it and docs/18-utilities.md had a diagram of it, both under the heading of a limitation rather than a defect, and there was no test for it at all -- nor for full containment, nor for a shared edge. It is four comparisons now, on both axes. `<=` rather than `<` because akgl_collide_point_rectangle is inclusive on all four edges and these two have always agreed that touching counts; a span test written with `<` would have silently changed a contract both the header and the manual state. The test was written first and failed on the cross before the fix went in. Two answers change for a caller upgrading, and both are in the header note and the chapter: - The cross reports `true`, which is the point. - The comparison is in float rather than through akgl_Point's int members, so a sub-pixel overlap is no longer truncated away. A pickup test that was accidentally forgiving by up to a pixel is no longer forgiving. Both tutorials use this for coins and hazards; both still pass. Faster as a side effect rather than a goal, and worth recording because the numbers move a documented budget: 24.9 ns -> 6.1 overlapping, 57.9 -> 6.1 disjoint, and the all-pairs sweep over 64 actors 115 us -> 12.2. The disjoint case gained most because it was the one that ran all eight tests before answering. The three moved rows are re-recorded in PERFORMANCE.md and nothing else is. akgl_rectangle_points is untouched by this change and reads 6.1 in the same run against the 4.0 recorded, so 6 ns is this run's floor and the new figure means "too cheap to measure" rather than "exactly 6.1" -- said in the prose so the next reader does not re-baseline the table around it. Co-Authored-By: Claude Code <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
558 lines
19 KiB
C
558 lines
19 KiB
C
#include <SDL3/SDL.h>
|
|
#include <string.h>
|
|
#include <akerror.h>
|
|
#include <akgl/error.h>
|
|
#include <akgl/heap.h>
|
|
#include <akgl/staticstring.h>
|
|
#include <akgl/util.h>
|
|
|
|
#include "testutil.h"
|
|
|
|
/**
|
|
* @brief How many entries of AKERR_ARRAY_ERROR are currently held by somebody.
|
|
*
|
|
* The error contexts are a fixed pool exactly like the object pools: a context
|
|
* is in use while its reference count is non-zero, and a function that finishes
|
|
* without releasing one has leaked a slot out of AKERR_MAX_ARRAY_ERROR.
|
|
*/
|
|
static int live_error_contexts(void)
|
|
{
|
|
int live = 0;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < AKERR_MAX_ARRAY_ERROR; i++ ) {
|
|
if ( AKERR_ARRAY_ERROR[i].refcount != 0 ) {
|
|
live += 1;
|
|
}
|
|
}
|
|
return live;
|
|
}
|
|
|
|
akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(void)
|
|
{
|
|
akgl_RectanglePoints points;
|
|
// Zeroed for the same reason as the fixtures in
|
|
// test_akgl_collide_point_rectangle_nullpointers: the last case here is a
|
|
// real call, and feeding it stack garbage is noise under `memcheck`.
|
|
SDL_FRect testrect = {.x = 0, .y = 0, .w = 0, .h = 0};
|
|
PREPARE_ERROR(errctx);
|
|
|
|
memset((void *)&points, 0x00, sizeof(akgl_RectanglePoints));
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_rectangle_points(NULL, NULL));
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_rectangle_points fails to FAIL with all NULL pointers");
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
|
// noop
|
|
} FINISH(errctx, true);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_rectangle_points(NULL, &testrect));
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_rectangle_points fails to FAIL with NULL SDL_FRect pointer");
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
|
// noop
|
|
} FINISH(errctx, true);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_rectangle_points(&points, NULL));
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_rectangle_points fails to FAIL with NULL akgl_RectanglePoints pointer");
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
|
// noop
|
|
} FINISH(errctx, true);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_rectangle_points(&points, &testrect));
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} FINISH(errctx, true);
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *test_akgl_rectangle_points_math(void)
|
|
{
|
|
akgl_RectanglePoints points;
|
|
SDL_FRect testrect = {.x = 0, .y = 0, .w = 32, .h = 32};
|
|
memset((void *)&points, 0x00, sizeof(akgl_RectanglePoints));
|
|
|
|
PREPARE_ERROR(errctx);
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_rectangle_points(&points, &testrect));
|
|
if ( points.topleft.x != 0 ||
|
|
points.topleft.y != 0 ||
|
|
points.topright.x != 32 ||
|
|
points.topright.y != 0 ||
|
|
points.bottomleft.x != 0 ||
|
|
points.bottomleft.y != 32 ||
|
|
points.bottomright.x != 32 ||
|
|
points.bottomright.y != 32 ) {
|
|
FAIL_BREAK(
|
|
errctx,
|
|
AKGL_ERR_BEHAVIOR,
|
|
"akgl_rectangle_points incorrectly calculated points for {x=0, y=0, w=32, h=32} to {topleft={%d, %d}, topright={%d, %d}, bottomleft={%d, %d}, bottomright={%d, %d}}",
|
|
points.topleft.x, points.topleft.y,
|
|
points.topright.x, points.topright.y,
|
|
points.bottomleft.x, points.bottomleft.y,
|
|
points.bottomright.x, points.bottomright.y
|
|
);
|
|
}
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} FINISH(errctx, true);
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *test_akgl_collide_point_rectangle_nullpointers(void)
|
|
{
|
|
// Zeroed rather than left as whatever the stack held. The last case in this
|
|
// function is a real call with real arguments, and reading uninitialised
|
|
// floats out of it is sixteen findings under `memcheck` for a test that is
|
|
// not about coordinates at all.
|
|
akgl_Point testpoint = { .x = 0, .y = 0 };
|
|
akgl_RectanglePoints testrectpoints;
|
|
bool testcollide = false;
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
memset(&testrectpoints, 0x00, sizeof(akgl_RectanglePoints));
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, &testrectpoints, NULL));
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_point_rectangle(*, *, NULL) failed");
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
|
// noop
|
|
} FINISH(errctx, true);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, NULL, &testcollide));
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_point_rectangle(*, NULL, *) failed");
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
|
// noop
|
|
} FINISH(errctx, true);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_collide_point_rectangle(NULL, &testrectpoints, &testcollide));
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_point_rectangle(NULL, *, *) failed");
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
|
// noop
|
|
} FINISH(errctx, true);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_collide_point_rectangle(NULL, NULL, NULL));
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_point_rectangle(NULL, NULL, NULL) failed");
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
|
// noop
|
|
} FINISH(errctx, true);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, &testrectpoints, &testcollide));
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} FINISH(errctx, true);
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *test_akgl_collide_point_rectangle_logic(void)
|
|
{
|
|
akgl_Point testpoint = {.x = 16, .y = 16};
|
|
SDL_FRect testrect = { .x = 0, .y = 0, .w = 32, .h = 32};
|
|
akgl_RectanglePoints testrectpoints;
|
|
bool testcollide = false;
|
|
PREPARE_ERROR(errctx);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_rectangle_points(&testrectpoints, &testrect));
|
|
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, &testrectpoints, &testcollide));
|
|
if ( testcollide == false ) {
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
|
}
|
|
|
|
testpoint.x = 48;
|
|
testpoint.y = 48;
|
|
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, &testrectpoints, &testcollide));
|
|
if ( testcollide == true ) {
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Invalid collision reported");
|
|
}
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} FINISH(errctx, true);
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *test_akgl_collide_rectangles_nullpointers(void)
|
|
{
|
|
SDL_FRect testrect1 = {.x = 0, .y = 0, .w = 0, .h = 0};
|
|
SDL_FRect testrect2 = {.x = 0, .y = 0, .w = 0, .h = 0};
|
|
bool testcollide = false;
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, NULL));
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_rectangles(*, *, NULL) failed");
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
|
// noop
|
|
} FINISH(errctx, true);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, NULL, &testcollide));
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_rectangles(*, NULL, *) failed");
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
|
// noop
|
|
} FINISH(errctx, true);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_collide_rectangles(NULL, &testrect2, &testcollide));
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_rectangles(NULL, *, *) failed");
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
|
// noop
|
|
} FINISH(errctx, true);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_collide_rectangles(NULL, NULL, NULL));
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_rectangles(NULL, NULL, NULL) failed");
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
|
// noop
|
|
} FINISH(errctx, true);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} FINISH(errctx, true);
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/**
|
|
* @brief The arrangements corner containment cannot see, and the ones it must keep.
|
|
*
|
|
* Eight corner-in-rectangle tests answer "do these overlap" correctly only when
|
|
* one rectangle encloses a corner of the other. A tall thin rectangle crossing a
|
|
* short wide one overlaps in a plus sign with no corner inside either, and eight
|
|
* corner tests all report false. That was documented on akgl_collide_rectangles
|
|
* as a known limitation rather than fixed, and there was no test for it.
|
|
*
|
|
* The three cases below the cross are the ones a rewrite can break while fixing
|
|
* it: touching edges must keep counting as a collision, because
|
|
* akgl_collide_point_rectangle is inclusive on all four edges and both headers
|
|
* promise it; full containment must keep working; and a separation of less than
|
|
* one pixel must be seen, which the old implementation could not do because it
|
|
* routed through akgl_Point and truncated float to int.
|
|
*/
|
|
akerr_ErrorContext *test_akgl_collide_rectangles_arrangements(void)
|
|
{
|
|
SDL_FRect tall = { .x = 10.0f, .y = 0.0f, .w = 4.0f, .h = 40.0f };
|
|
SDL_FRect wide = { .x = 0.0f, .y = 10.0f, .w = 40.0f, .h = 4.0f };
|
|
SDL_FRect outer = { .x = 0.0f, .y = 0.0f, .w = 64.0f, .h = 64.0f };
|
|
SDL_FRect inner = { .x = 16.0f, .y = 16.0f, .w = 8.0f, .h = 8.0f };
|
|
SDL_FRect left = { .x = 0.0f, .y = 0.0f, .w = 10.0f, .h = 10.0f };
|
|
SDL_FRect right = { .x = 10.0f, .y = 0.0f, .w = 10.0f, .h = 10.0f };
|
|
SDL_FRect near1 = { .x = 0.0f, .y = 0.0f, .w = 10.9f, .h = 10.0f };
|
|
SDL_FRect near2 = { .x = 10.5f, .y = 0.0f, .w = 10.0f, .h = 10.0f };
|
|
bool collide = false;
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
ATTEMPT {
|
|
// The cross. Neither rectangle encloses a corner of the other, and they
|
|
// plainly overlap in the middle.
|
|
CATCH(errctx, akgl_collide_rectangles(&tall, &wide, &collide));
|
|
TEST_ASSERT(errctx, (collide == true),
|
|
"a tall rectangle crossing a wide one was reported as not colliding");
|
|
|
|
// The same pair the other way round. The test is symmetric and has to be.
|
|
CATCH(errctx, akgl_collide_rectangles(&wide, &tall, &collide));
|
|
TEST_ASSERT(errctx, (collide == true),
|
|
"the crossing pair was reported as not colliding with the arguments swapped");
|
|
|
|
// Full containment, both orders.
|
|
CATCH(errctx, akgl_collide_rectangles(&outer, &inner, &collide));
|
|
TEST_ASSERT(errctx, (collide == true), "a contained rectangle was missed");
|
|
CATCH(errctx, akgl_collide_rectangles(&inner, &outer, &collide));
|
|
TEST_ASSERT(errctx, (collide == true), "a containing rectangle was missed");
|
|
|
|
// A shared edge and nothing more. akgl_collide_point_rectangle is
|
|
// inclusive on all four edges, so touching counts; util.h and chapter 18
|
|
// both say so, and a span test written with < rather than <= silently
|
|
// changes that.
|
|
CATCH(errctx, akgl_collide_rectangles(&left, &right, &collide));
|
|
TEST_ASSERT(errctx, (collide == true),
|
|
"two rectangles sharing exactly one edge were reported as not colliding");
|
|
|
|
// Overlapping by four tenths of a pixel. The corner tests truncated
|
|
// float to int on the way in, so this read as a shared edge at 10.
|
|
CATCH(errctx, akgl_collide_rectangles(&near1, &near2, &collide));
|
|
TEST_ASSERT(errctx, (collide == true), "a sub-pixel overlap was missed");
|
|
|
|
// Separated by four tenths of a pixel. Truncation read this as touching.
|
|
near2.x = 11.3f;
|
|
CATCH(errctx, akgl_collide_rectangles(&near1, &near2, &collide));
|
|
TEST_ASSERT(errctx, (collide == false), "a sub-pixel gap was reported as a collision");
|
|
|
|
// Disjoint on one axis only, which is the case a span test gets wrong
|
|
// when it forgets to check both.
|
|
tall.x = 100.0f;
|
|
CATCH(errctx, akgl_collide_rectangles(&tall, &wide, &collide));
|
|
TEST_ASSERT(errctx, (collide == false),
|
|
"rectangles separated on x were reported as colliding");
|
|
tall.x = 10.0f;
|
|
tall.y = 100.0f;
|
|
CATCH(errctx, akgl_collide_rectangles(&tall, &wide, &collide));
|
|
TEST_ASSERT(errctx, (collide == false),
|
|
"rectangles separated on y were reported as colliding");
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} FINISH(errctx, true);
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *test_akgl_collide_rectangles_logic(void)
|
|
{
|
|
SDL_FRect testrect1 = { .x = 0, .y = 0, .w = 32, .h = 32};
|
|
SDL_FRect testrect2 = { .x = 30, .y = 30, .w = 40, .h = 40};
|
|
bool testcollide = false;
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
ATTEMPT {
|
|
// Collision overlapping on the top left
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
|
if ( testcollide == false ) {
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
|
}
|
|
|
|
// Collision overlapping on the top right
|
|
testrect1.x = 64;
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
|
if ( testcollide == false ) {
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
|
}
|
|
|
|
// Collision overlapping on the bottom left
|
|
testrect1.x = 0;
|
|
testrect1.y = 32;
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
|
if ( testcollide == false ) {
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
|
}
|
|
|
|
// Collision overlapping on the bottom right
|
|
testrect1.x = 32;
|
|
testrect1.y = 32;
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
|
if ( testcollide == false ) {
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
|
}
|
|
|
|
// Collision overlapping the top edge
|
|
testrect1.x = 0;
|
|
testrect1.y = 0;
|
|
testrect1.w = 60;
|
|
testrect1.h = 32;
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
|
if ( testcollide == false ) {
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
|
}
|
|
|
|
// Collision overlapping the left edge
|
|
testrect1.x = 0;
|
|
testrect1.y = 0;
|
|
testrect1.w = 35;
|
|
testrect1.h = 80;
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
|
if ( testcollide == false ) {
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
|
}
|
|
|
|
// Collision overlapping the right edge
|
|
testrect1.x = 65;
|
|
testrect1.y = 0;
|
|
testrect1.w = 60;
|
|
testrect1.h = 80;
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
|
if ( testcollide == false ) {
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
|
}
|
|
|
|
// Collision overlapping the bottom edge
|
|
testrect1.x = 0;
|
|
testrect1.y = 65;
|
|
testrect1.w = 80;
|
|
testrect1.h = 32;
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
|
if ( testcollide == false ) {
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
|
}
|
|
|
|
// Not colliding
|
|
testrect1.x = 0;
|
|
testrect1.y = 0;
|
|
testrect1.w = 16;
|
|
testrect1.h = 16;
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
|
if ( testcollide == true ) {
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Invalid collision reported");
|
|
}
|
|
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} FINISH(errctx, true);
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Resolving a path through the root fallback must give its context back.
|
|
*
|
|
* akgl_path_relative tries the working directory first and falls back to
|
|
* resolving against @p root when that reports ENOENT. That fallback used to be
|
|
* taken by returning from inside the HANDLE block, which skips the
|
|
* RELEASE_ERROR that FINISH ends with -- so every call down that branch leaked
|
|
* one entry of AKERR_ARRAY_ERROR, and the 129th call aborted the whole process
|
|
* with "Unable to pull an error context from the array!". A single map load
|
|
* resolves several paths this way.
|
|
*
|
|
* The loop runs well past AKERR_MAX_ARRAY_ERROR on purpose: at the old
|
|
* behaviour this test does not fail, it terminates the suite.
|
|
*/
|
|
/**
|
|
* @brief akgl_compare_sdl_surfaces must check geometry before it memcmps.
|
|
*
|
|
* It compared `s1->pitch * s1->h` bytes out of both surfaces without looking at
|
|
* the second one's dimensions, so a smaller s2 was read past its end rather
|
|
* than reported as a mismatch. Benign in practice and immediately fatal under a
|
|
* memory checker, which is the reason to fix it rather than leave it.
|
|
*/
|
|
akerr_ErrorContext *test_akgl_compare_sdl_surfaces_checks_geometry(void)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
SDL_Surface *big = NULL;
|
|
SDL_Surface *small = NULL;
|
|
SDL_Surface *twin = NULL;
|
|
|
|
ATTEMPT {
|
|
big = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_RGBA8888);
|
|
twin = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_RGBA8888);
|
|
small = SDL_CreateSurface(8, 8, SDL_PIXELFORMAT_RGBA8888);
|
|
FAIL_ZERO_BREAK(errctx, big, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
FAIL_ZERO_BREAK(errctx, twin, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
FAIL_ZERO_BREAK(errctx, small, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
FAIL_ZERO_BREAK(errctx, SDL_FillSurfaceRect(big, NULL, 0), AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
FAIL_ZERO_BREAK(errctx, SDL_FillSurfaceRect(twin, NULL, 0), AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
FAIL_ZERO_BREAK(errctx, SDL_FillSurfaceRect(small, NULL, 0), AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
TEST_EXPECT_OK(errctx, akgl_compare_sdl_surfaces(big, twin),
|
|
"comparing two identical surfaces");
|
|
|
|
// The one that used to read 4 KiB past the end of an 8x8 surface.
|
|
TEST_EXPECT_STATUS(errctx, AKERR_VALUE, akgl_compare_sdl_surfaces(big, small),
|
|
"comparing a 32x32 surface against an 8x8 one");
|
|
TEST_EXPECT_STATUS(errctx, AKERR_VALUE, akgl_compare_sdl_surfaces(small, big),
|
|
"comparing an 8x8 surface against a 32x32 one");
|
|
|
|
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_compare_sdl_surfaces(NULL, big),
|
|
"comparing a NULL first surface");
|
|
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_compare_sdl_surfaces(big, NULL),
|
|
"comparing a NULL second surface");
|
|
|
|
// Same dimensions, different format: the pixels are not comparable even
|
|
// though the byte count might be.
|
|
SDL_DestroySurface(small);
|
|
small = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_RGB24);
|
|
FAIL_ZERO_BREAK(errctx, small, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
TEST_EXPECT_STATUS(errctx, AKERR_VALUE, akgl_compare_sdl_surfaces(big, small),
|
|
"comparing two surfaces of different pixel formats");
|
|
} CLEANUP {
|
|
if ( big != NULL ) {
|
|
SDL_DestroySurface(big);
|
|
}
|
|
if ( twin != NULL ) {
|
|
SDL_DestroySurface(twin);
|
|
}
|
|
if ( small != NULL ) {
|
|
SDL_DestroySurface(small);
|
|
}
|
|
} PROCESS(errctx) {
|
|
} FINISH(errctx, true);
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *test_akgl_path_relative_releases_contexts(void)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akgl_String *dst = NULL;
|
|
int before = 0;
|
|
int after = 0;
|
|
int i = 0;
|
|
|
|
PASS(errctx, akgl_heap_init());
|
|
PASS(errctx, akgl_heap_next_string(&dst));
|
|
before = live_error_contexts();
|
|
|
|
for ( i = 0; i < (AKERR_MAX_ARRAY_ERROR * 2); i++ ) {
|
|
PASS(errctx, akgl_path_relative("assets", "testcharacter.json", dst));
|
|
}
|
|
after = live_error_contexts();
|
|
|
|
ATTEMPT {
|
|
if ( after != before ) {
|
|
FAIL_BREAK(
|
|
errctx,
|
|
AKGL_ERR_BEHAVIOR,
|
|
"akgl_path_relative leaked %d error context(s) over %d root-fallback resolutions",
|
|
(after - before),
|
|
(AKERR_MAX_ARRAY_ERROR * 2));
|
|
}
|
|
} CLEANUP {
|
|
IGNORE(akgl_heap_release_string(dst));
|
|
} PROCESS(errctx) {
|
|
} FINISH(errctx, true);
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_error_init());
|
|
CATCH(errctx, test_akgl_rectangle_points_nullpointers());
|
|
CATCH(errctx, test_akgl_rectangle_points_math());
|
|
CATCH(errctx, test_akgl_collide_point_rectangle_nullpointers());
|
|
// Defined since forever and never called until 0.5.0. TODO.md, "Known
|
|
// and still open" item 9.
|
|
CATCH(errctx, test_akgl_collide_point_rectangle_logic());
|
|
CATCH(errctx, test_akgl_collide_rectangles_nullpointers());
|
|
CATCH(errctx, test_akgl_collide_rectangles_logic());
|
|
CATCH(errctx, test_akgl_collide_rectangles_arrangements());
|
|
CATCH(errctx, test_akgl_compare_sdl_surfaces_checks_geometry());
|
|
CATCH(errctx, test_akgl_path_relative_releases_contexts());
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} FINISH_NORETURN(errctx);
|
|
}
|