1
0
Fork 0

tests/clutter/timeline-interpolate: Rework test to be less flaky

This more or less rewrites this test so that it explicitly tests the
"interpolation" when a timeline loops, i.e. that if something occupies
the thread when a timeline was supposed to have looped, we end up in the
right place "in the middle" of the next timeline cycle.

The test more or less does this:

 * Start a 3 second looping timeline
 * Sleep so that we're in the middle of the first cycle
 * Sleep again so that we end up in the middle of the next cycle

The semantics checked are that we see the following frames:

 * The first frame with timestamp 0
 * The second frame in the middle of the first cycle (timestamp ~= 1.5
   sceonds)
 * The third frame in the end of the first cycle (timestamp == 3.0
   seconds)
 * The fourth frame, first in the second cycle, with timestamp ~= 1.5
   seconds)

This means we can increase the "grace period" to the double (from 0.5 s
to 1 s), while at the same time decrease the time spent running the test
(from 10 s to 4.5 s). This should hopefully make the test less flaky,
especially in slower runners, e.g. aarch64.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1751>
This commit is contained in:
Jonas Ådahl 2021-03-01 14:38:32 +01:00 committed by Marge Bot
parent 0d3840b056
commit 6f30764320

View file

@ -5,21 +5,13 @@
#include "tests/clutter-test-utils.h" #include "tests/clutter-test-utils.h"
/* We ask for 1 frame per millisecond. #define TEST_TIMELINE_DURATION 3000
* Whenever this rate can't be achieved then the timeline
* will interpolate the number frames that should have
* passed between timeouts. */
#define TEST_TIMELINE_FPS 1000
#define TEST_TIMELINE_DURATION 5000
/* We are at the mercy of the system scheduler so this /*
* may not be a very reliable tolerance. * Make the test tolarate being half a second off track in each direction,
* * the thing we're testing for will still be tested for.
* It's set as very tolerable (1 ms shorter than the frame interval) as
* otherwise CI, which are very prone to not get CPU time scheduled, tend to
* often fail.
*/ */
#define TEST_ERROR_TOLERANCE ((TEST_TIMELINE_FPS / 4) - 1) #define TEST_ERROR_TOLERANCE 500
typedef struct _TestState typedef struct _TestState
{ {
@ -28,7 +20,7 @@ typedef struct _TestState
int new_frame_counter; int new_frame_counter;
int expected_frame; int expected_frame;
int completion_count; int completion_count;
gboolean passed; int cycle_frame_counter;
} TestState; } TestState;
@ -41,7 +33,6 @@ new_frame_cb (ClutterTimeline *timeline,
int current_frame; int current_frame;
long msec_diff; long msec_diff;
int loop_overflow = 0; int loop_overflow = 0;
static int step = 1;
current_time = g_get_real_time (); current_time = g_get_real_time ();
@ -61,57 +52,100 @@ new_frame_cb (ClutterTimeline *timeline,
state->expected_frame = TEST_TIMELINE_DURATION; state->expected_frame = TEST_TIMELINE_DURATION;
} }
if (current_frame >= (state->expected_frame-TEST_ERROR_TOLERANCE) switch (state->cycle_frame_counter)
&& current_frame <= (state->expected_frame+TEST_ERROR_TOLERANCE))
{ {
g_test_message ("elapsed milliseconds=%-5li " case 0:
"expected frame=%-4i actual frame=%-4i (OK)", case 1:
msec_diff, if (current_frame >= (state->expected_frame - TEST_ERROR_TOLERANCE) &&
state->expected_frame, current_frame <= (state->expected_frame + TEST_ERROR_TOLERANCE))
current_frame); {
} g_test_message ("elapsed milliseconds=%-5li "
else "expected frame=%-4i actual frame=%-4i (OK)",
{ msec_diff,
g_test_message ("elapsed milliseconds=%-5li " state->expected_frame,
"expected frame=%-4i actual frame=%-4i (FAILED)", current_frame);
msec_diff, }
state->expected_frame, else
current_frame); {
state->passed = FALSE; g_test_message ("elapsed milliseconds=%-5li "
"expected frame=%-4i actual frame=%-4i (FAILED)",
msec_diff,
state->expected_frame,
current_frame);
g_test_fail ();
}
break;
case 2:
g_assert_cmpint (current_frame, ==, TEST_TIMELINE_DURATION);
break;
default:
g_assert_not_reached ();
} }
if (step>0) /* We already tested that we interpolated when looping, lets stop now. */
if (state->completion_count == 1 &&
state->cycle_frame_counter == 0)
{ {
state->expected_frame = current_frame + (TEST_TIMELINE_FPS / 4); clutter_timeline_stop (timeline);
g_test_message ("Sleeping for 250ms " return;
"so next frame should be (%i + %i) = %i",
current_frame,
(TEST_TIMELINE_FPS / 4),
state->expected_frame);
g_usleep (250000);
}
else
{
state->expected_frame = current_frame + TEST_TIMELINE_FPS;
g_test_message ("Sleeping for 1sec "
"so next frame should be (%i + %i) = %i",
current_frame,
TEST_TIMELINE_FPS,
state->expected_frame);
g_usleep (1000000);
} }
if (current_frame >= TEST_TIMELINE_DURATION) switch (state->cycle_frame_counter)
{ {
state->expected_frame += loop_overflow; case 0:
state->expected_frame -= TEST_TIMELINE_DURATION; {
g_test_message ("End of timeline reached: " /*
"Wrapping expected frame too %i", * First frame, sleep so we're about in the middle of the cycle,
state->expected_frame); * before the end of the timeline cycle.
*/
int delay_ms = ms (1500);
state->expected_frame = current_frame + delay_ms;
g_test_message ("Sleeping for 1.5 seconds "
"so next frame should be (%d + %d) = %d",
current_frame,
delay_ms,
state->expected_frame);
g_usleep (ms2us (delay_ms));
break;
}
case 1:
{
/*
* Second frame, we're about in the middle of the cycle; sleep one cycle,
* and check that we end up in the middle again.
*/
int delay_ms = TEST_TIMELINE_DURATION;
state->expected_frame = current_frame + delay_ms;
g_test_message ("Sleeping for %d seconds "
"so next frame should be (%d + %d) = %d, "
"which is %d into the next cycle",
TEST_TIMELINE_DURATION / 1000,
current_frame,
delay_ms,
state->expected_frame,
state->expected_frame - TEST_TIMELINE_DURATION);
g_usleep (ms2us (delay_ms));
g_assert_cmpint (state->expected_frame, >, TEST_TIMELINE_DURATION);
state->expected_frame += loop_overflow;
state->expected_frame -= TEST_TIMELINE_DURATION;
g_test_message ("End of timeline reached: "
"Wrapping expected frame too %d",
state->expected_frame);
break;
}
case 2:
case 3:
{
break;
}
} }
state->new_frame_counter++; state->new_frame_counter++;
step = -step; state->cycle_frame_counter++;
} }
static void static void
@ -119,14 +153,20 @@ completed_cb (ClutterTimeline *timeline,
TestState *state) TestState *state)
{ {
state->completion_count++; state->completion_count++;
state->cycle_frame_counter = 0;
if (state->completion_count == 2) if (state->completion_count >= 2)
{ g_assert_not_reached ();
if (state->passed) }
clutter_test_quit ();
else static void
g_assert_not_reached (); stopped_cb (ClutterTimeline *timeline,
} gboolean is_finished,
TestState *state)
{
g_assert_cmpint (state->completion_count, ==, 1);
clutter_test_quit ();
} }
static void static void
@ -148,10 +188,14 @@ timeline_interpolation (void)
"completed", "completed",
G_CALLBACK (completed_cb), G_CALLBACK (completed_cb),
&state); &state);
g_signal_connect (state.timeline,
"stopped",
G_CALLBACK (stopped_cb),
&state);
state.completion_count = 0; state.completion_count = 0;
state.new_frame_counter = 0; state.new_frame_counter = 0;
state.passed = TRUE; state.cycle_frame_counter = 0;
state.expected_frame = 0; state.expected_frame = 0;
clutter_actor_show (stage); clutter_actor_show (stage);