blob: 9b70d92eac334ff97ea533f0910f96507c7b3bff [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
#pragma clang diagnostic ignored "-Wextra"
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
#define LOG_NDEBUG 0
#include <thread>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <scheduler/TimeKeeper.h>
#include <common/test/FlagUtils.h>
#include "Scheduler/VSyncDispatchTimerQueue.h"
#include "Scheduler/VSyncTracker.h"
#include "mock/MockVSyncTracker.h"
#include <com_android_graphics_surfaceflinger_flags.h>
using namespace testing;
using namespace std::literals;
namespace android::scheduler {
using namespace com::android::graphics::surfaceflinger;
class MockVSyncTracker : public mock::VSyncTracker {
public:
MockVSyncTracker(nsecs_t period) : mPeriod{period} {
ON_CALL(*this, nextAnticipatedVSyncTimeFrom(_, _))
.WillByDefault(Invoke(this, &MockVSyncTracker::nextVSyncTime));
ON_CALL(*this, addVsyncTimestamp(_)).WillByDefault(Return(true));
ON_CALL(*this, currentPeriod())
.WillByDefault(Invoke(this, &MockVSyncTracker::getCurrentPeriod));
}
nsecs_t nextVSyncTime(nsecs_t timePoint, std::optional<nsecs_t>) const {
if (timePoint % mPeriod == 0) {
return timePoint;
}
return (timePoint - (timePoint % mPeriod) + mPeriod);
}
nsecs_t getCurrentPeriod() const { return mPeriod; }
protected:
nsecs_t const mPeriod;
};
class ControllableClock : public TimeKeeper {
public:
ControllableClock() {
ON_CALL(*this, alarmAt(_, _))
.WillByDefault(Invoke(this, &ControllableClock::alarmAtDefaultBehavior));
ON_CALL(*this, now()).WillByDefault(Invoke(this, &ControllableClock::fakeTime));
}
MOCK_METHOD(nsecs_t, now, (), (const));
MOCK_METHOD(void, alarmAt, (std::function<void()>, nsecs_t), (override));
MOCK_METHOD(void, alarmCancel, (), (override));
MOCK_METHOD(void, dump, (std::string&), (const, override));
void alarmAtDefaultBehavior(std::function<void()> const& callback, nsecs_t time) {
mCallback = callback;
mNextCallbackTime = time;
}
nsecs_t fakeTime() const { return mCurrentTime; }
void advanceToNextCallback() {
mCurrentTime = mNextCallbackTime;
if (mCallback) {
mCallback();
}
}
void advanceBy(nsecs_t advancement) {
mCurrentTime += advancement;
if (mCurrentTime >= (mNextCallbackTime + mLag) && mCallback) {
mCallback();
}
};
void setLag(nsecs_t lag) { mLag = lag; }
private:
std::function<void()> mCallback;
nsecs_t mNextCallbackTime = 0;
nsecs_t mCurrentTime = 0;
nsecs_t mLag = 0;
};
class CountingCallback {
public:
CountingCallback(std::shared_ptr<VSyncDispatch> dispatch)
: mDispatch(std::move(dispatch)),
mToken(mDispatch->registerCallback(std::bind(&CountingCallback::counter, this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3),
"test")) {}
~CountingCallback() { mDispatch->unregisterCallback(mToken); }
operator VSyncDispatch::CallbackToken() const { return mToken; }
void counter(nsecs_t time, nsecs_t wakeup_time, nsecs_t readyTime) {
mCalls.push_back(time);
mWakeupTime.push_back(wakeup_time);
mReadyTime.push_back(readyTime);
}
std::shared_ptr<VSyncDispatch> mDispatch;
VSyncDispatch::CallbackToken mToken;
std::vector<nsecs_t> mCalls;
std::vector<nsecs_t> mWakeupTime;
std::vector<nsecs_t> mReadyTime;
};
class PausingCallback {
public:
PausingCallback(std::shared_ptr<VSyncDispatch> dispatch, std::chrono::milliseconds pauseAmount)
: mDispatch(std::move(dispatch)),
mToken(mDispatch->registerCallback(std::bind(&PausingCallback::pause, this,
std::placeholders::_1,
std::placeholders::_2),
"test")),
mRegistered(true),
mPauseAmount(pauseAmount) {}
~PausingCallback() { unregister(); }
operator VSyncDispatch::CallbackToken() const { return mToken; }
void pause(nsecs_t, nsecs_t) {
std::unique_lock lock(mMutex);
mPause = true;
mCv.notify_all();
mCv.wait_for(lock, mPauseAmount, [this] { return !mPause; });
mResourcePresent = (mResource.lock() != nullptr);
}
bool waitForPause() {
std::unique_lock lock(mMutex);
auto waiting = mCv.wait_for(lock, 10s, [this] { return mPause; });
return waiting;
}
void stashResource(std::weak_ptr<void> const& resource) { mResource = resource; }
bool resourcePresent() { return mResourcePresent; }
void unpause() {
std::unique_lock lock(mMutex);
mPause = false;
mCv.notify_all();
}
void unregister() {
if (mRegistered) {
mDispatch->unregisterCallback(mToken);
mRegistered = false;
}
}
std::shared_ptr<VSyncDispatch> mDispatch;
VSyncDispatch::CallbackToken mToken;
bool mRegistered = true;
std::mutex mMutex;
std::condition_variable mCv;
bool mPause = false;
std::weak_ptr<void> mResource;
bool mResourcePresent = false;
std::chrono::milliseconds const mPauseAmount;
};
class VSyncDispatchTimerQueueTest : public testing::Test {
protected:
std::unique_ptr<TimeKeeper> createTimeKeeper() {
class TimeKeeperWrapper : public TimeKeeper {
public:
TimeKeeperWrapper(TimeKeeper& control) : mControllableClock(control) {}
nsecs_t now() const final { return mControllableClock.now(); }
void alarmAt(std::function<void()> callback, nsecs_t time) final {
mControllableClock.alarmAt(std::move(callback), time);
}
void alarmCancel() final { mControllableClock.alarmCancel(); }
void dump(std::string&) const final {}
private:
TimeKeeper& mControllableClock;
};
return std::make_unique<TimeKeeperWrapper>(mMockClock);
}
~VSyncDispatchTimerQueueTest() {
// destructor of dispatch will cancelAlarm(). Ignore final cancel in common test.
Mock::VerifyAndClearExpectations(&mMockClock);
}
void advanceToNextCallback() { mMockClock.advanceToNextCallback(); }
NiceMock<ControllableClock> mMockClock;
static nsecs_t constexpr mDispatchGroupThreshold = 5;
nsecs_t const mPeriod = 1000;
nsecs_t const mVsyncMoveThreshold = 300;
std::shared_ptr<NiceMock<MockVSyncTracker>> mStubTracker =
std::make_shared<NiceMock<MockVSyncTracker>>(mPeriod);
std::shared_ptr<VSyncDispatch> mDispatch =
std::make_shared<VSyncDispatchTimerQueue>(createTimeKeeper(), mStubTracker,
mDispatchGroupThreshold, mVsyncMoveThreshold);
};
TEST_F(VSyncDispatchTimerQueueTest, unregistersSetAlarmOnDestruction) {
EXPECT_CALL(mMockClock, alarmAt(_, 900));
EXPECT_CALL(mMockClock, alarmCancel());
{
std::shared_ptr<VSyncDispatch> mDispatch =
std::make_shared<VSyncDispatchTimerQueue>(createTimeKeeper(), mStubTracker,
mDispatchGroupThreshold,
mVsyncMoveThreshold);
CountingCallback cb(mDispatch);
const auto result =
mDispatch->schedule(cb,
{.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(900, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
}
}
TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFuture) {
auto intended = mPeriod - 230;
EXPECT_CALL(mMockClock, alarmAt(_, 900));
CountingCallback cb(mDispatch);
const auto result =
mDispatch->schedule(cb,
{.workDuration = 100, .readyDuration = 0, .lastVsync = intended});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(900, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
EXPECT_THAT(cb.mCalls[0], Eq(mPeriod));
}
TEST_F(VSyncDispatchTimerQueueTest, updateAlarmSettingFuture) {
auto intended = mPeriod - 230;
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 700)).InSequence(seq);
CountingCallback cb(mDispatch);
auto result =
mDispatch->schedule(cb,
{.workDuration = 100, .readyDuration = 0, .lastVsync = intended});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(900, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
result =
mDispatch->update(cb, {.workDuration = 300, .readyDuration = 0, .lastVsync = intended});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(700, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
EXPECT_THAT(cb.mCalls[0], Eq(mPeriod));
EXPECT_THAT(cb.mWakeupTime[0], Eq(700));
}
TEST_F(VSyncDispatchTimerQueueTest, updateDoesntSchedule) {
auto intended = mPeriod - 230;
EXPECT_CALL(mMockClock, alarmAt(_, _)).Times(0);
CountingCallback cb(mDispatch);
const auto result =
mDispatch->update(cb, {.workDuration = 300, .readyDuration = 0, .lastVsync = intended});
EXPECT_FALSE(result.has_value());
}
TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithAdjustmentToTrueVsync) {
EXPECT_CALL(*mStubTracker.get(),
nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(mPeriod)))
.WillOnce(Return(1150));
EXPECT_CALL(mMockClock, alarmAt(_, 1050));
CountingCallback cb(mDispatch);
mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
EXPECT_THAT(cb.mCalls[0], Eq(1150));
}
TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingAdjustmentPast) {
auto const now = 234;
mMockClock.advanceBy(234);
auto const workDuration = 10 * mPeriod;
EXPECT_CALL(*mStubTracker.get(),
nextAnticipatedVSyncTimeFrom(now + workDuration, std::optional<nsecs_t>(mPeriod)))
.WillOnce(Return(mPeriod * 11));
EXPECT_CALL(mMockClock, alarmAt(_, mPeriod));
CountingCallback cb(mDispatch);
const auto result = mDispatch->schedule(cb,
{.workDuration = workDuration,
.readyDuration = 0,
.lastVsync = mPeriod});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod, result->callbackTime.ns());
EXPECT_EQ(workDuration + mPeriod, result->vsyncTime.ns());
}
TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancel) {
EXPECT_CALL(mMockClock, alarmAt(_, 900));
EXPECT_CALL(mMockClock, alarmCancel());
CountingCallback cb(mDispatch);
const auto result =
mDispatch->schedule(cb,
{.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod - 100, result->callbackTime.ns());
EXPECT_EQ(mPeriod, result->vsyncTime.ns());
EXPECT_EQ(mDispatch->cancel(cb), CancelResult::Cancelled);
}
TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLate) {
EXPECT_CALL(mMockClock, alarmAt(_, 900));
EXPECT_CALL(mMockClock, alarmCancel());
CountingCallback cb(mDispatch);
const auto result =
mDispatch->schedule(cb,
{.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod - 100, result->callbackTime.ns());
EXPECT_EQ(mPeriod, result->vsyncTime.ns());
mMockClock.advanceBy(950);
EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate);
}
TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLateWhenRunning) {
EXPECT_CALL(mMockClock, alarmAt(_, 900));
EXPECT_CALL(mMockClock, alarmCancel());
PausingCallback cb(mDispatch, std::chrono::duration_cast<std::chrono::milliseconds>(1s));
const auto result =
mDispatch->schedule(cb,
{.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod - 100, result->callbackTime.ns());
EXPECT_EQ(mPeriod, result->vsyncTime.ns());
std::thread pausingThread([&] { mMockClock.advanceToNextCallback(); });
EXPECT_TRUE(cb.waitForPause());
EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate);
cb.unpause();
pausingThread.join();
}
TEST_F(VSyncDispatchTimerQueueTest, unregisterSynchronizes) {
EXPECT_CALL(mMockClock, alarmAt(_, 900));
EXPECT_CALL(mMockClock, alarmCancel());
auto resource = std::make_shared<int>(110);
PausingCallback cb(mDispatch, 50ms);
cb.stashResource(resource);
const auto result =
mDispatch->schedule(cb,
{.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod - 100, result->callbackTime.ns());
EXPECT_EQ(mPeriod, result->vsyncTime.ns());
std::thread pausingThread([&] { mMockClock.advanceToNextCallback(); });
EXPECT_TRUE(cb.waitForPause());
cb.unregister();
resource.reset();
cb.unpause();
pausingThread.join();
EXPECT_TRUE(cb.resourcePresent());
}
TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) {
EXPECT_CALL(*mStubTracker.get(),
nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
.Times(4)
.WillOnce(Return(1055))
.WillOnce(Return(1063))
.WillOnce(Return(1063))
.WillOnce(Return(1075));
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 955)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 813)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 975)).InSequence(seq);
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .lastVsync = mPeriod});
advanceToNextCallback();
advanceToNextCallback();
ASSERT_THAT(cb0.mCalls.size(), Eq(1));
EXPECT_THAT(cb0.mCalls[0], Eq(1075));
ASSERT_THAT(cb1.mCalls.size(), Eq(1));
EXPECT_THAT(cb1.mCalls[0], Eq(1063));
}
TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) {
EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
.Times(4)
.WillOnce(Return(1000))
.WillOnce(Return(2000))
.WillOnce(Return(2500))
.WillOnce(Return(4000));
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 3900)).InSequence(seq);
CountingCallback cb(mDispatch);
mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 0});
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
EXPECT_THAT(cb.mCalls[0], Eq(1000));
mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(2));
EXPECT_THAT(cb.mCalls[1], Eq(2000));
mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(3));
EXPECT_THAT(cb.mCalls[2], Eq(4000));
}
TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) {
EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
.Times(4)
.WillOnce(Return(10000))
.WillOnce(Return(1000))
.WillOnce(Return(10000))
.WillOnce(Return(10000));
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 9900)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 750)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 9900)).InSequence(seq);
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod * 10});
mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .lastVsync = mPeriod});
mDispatch->cancel(cb1);
}
TEST_F(VSyncDispatchTimerQueueTest, noUnnecessaryRearmsWhenRescheduling) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 700)).InSequence(seq);
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .lastVsync = 1000});
advanceToNextCallback();
}
TEST_F(VSyncDispatchTimerQueueTest, necessaryRearmsWhenModifying) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
advanceToNextCallback();
}
TEST_F(VSyncDispatchTimerQueueTest, modifyIntoGroup) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1600)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1590)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1600)).InSequence(seq);
auto offset = 400;
auto closeOffset = offset + mDispatchGroupThreshold - 1;
auto notCloseOffset = offset + 2 * mDispatchGroupThreshold;
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
mDispatch->schedule(cb1, {.workDuration = closeOffset, .readyDuration = 0, .lastVsync = 1000});
advanceToNextCallback();
ASSERT_THAT(cb0.mCalls.size(), Eq(1));
EXPECT_THAT(cb0.mCalls[0], Eq(mPeriod));
ASSERT_THAT(cb1.mCalls.size(), Eq(1));
EXPECT_THAT(cb1.mCalls[0], Eq(mPeriod));
mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 2000});
mDispatch->schedule(cb1,
{.workDuration = notCloseOffset, .readyDuration = 0, .lastVsync = 2000});
advanceToNextCallback();
ASSERT_THAT(cb1.mCalls.size(), Eq(2));
EXPECT_THAT(cb1.mCalls[1], Eq(2000));
advanceToNextCallback();
ASSERT_THAT(cb0.mCalls.size(), Eq(2));
EXPECT_THAT(cb0.mCalls[1], Eq(2000));
}
TEST_F(VSyncDispatchTimerQueueTest, rearmsWhenEndingAndDoesntCancel) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 800)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmCancel());
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
advanceToNextCallback();
EXPECT_EQ(mDispatch->cancel(cb0), CancelResult::Cancelled);
}
TEST_F(VSyncDispatchTimerQueueTest, setAlarmCallsAtCorrectTimeWithChangingVsync) {
EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
.Times(3)
.WillOnce(Return(950))
.WillOnce(Return(1975))
.WillOnce(Return(2950));
CountingCallback cb(mDispatch);
mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 920});
mMockClock.advanceBy(850);
EXPECT_THAT(cb.mCalls.size(), Eq(1));
mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1900});
mMockClock.advanceBy(900);
EXPECT_THAT(cb.mCalls.size(), Eq(1));
mMockClock.advanceBy(125);
EXPECT_THAT(cb.mCalls.size(), Eq(2));
mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2900});
mMockClock.advanceBy(975);
EXPECT_THAT(cb.mCalls.size(), Eq(3));
}
TEST_F(VSyncDispatchTimerQueueTest, callbackReentrancy) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
VSyncDispatch::CallbackToken tmp;
tmp = mDispatch->registerCallback(
[&](auto, auto, auto) {
mDispatch->schedule(tmp,
{.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
},
"o.o");
mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
advanceToNextCallback();
}
TEST_F(VSyncDispatchTimerQueueTest, callbackReentrantWithPastWakeup) {
VSyncDispatch::CallbackToken tmp;
std::optional<nsecs_t> lastTarget;
tmp = mDispatch->registerCallback(
[&](auto timestamp, auto, auto) {
auto result = mDispatch->schedule(tmp,
{.workDuration = 400,
.readyDuration = 0,
.lastVsync = timestamp - mVsyncMoveThreshold});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod + timestamp - 400, result->callbackTime.ns());
EXPECT_EQ(mPeriod + timestamp, result->vsyncTime.ns());
result = mDispatch->schedule(tmp,
{.workDuration = 400,
.readyDuration = 0,
.lastVsync = timestamp});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod + timestamp - 400, result->callbackTime.ns());
EXPECT_EQ(mPeriod + timestamp, result->vsyncTime.ns());
result = mDispatch->schedule(tmp,
{.workDuration = 400,
.readyDuration = 0,
.lastVsync = timestamp + mVsyncMoveThreshold});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod + timestamp - 400, result->callbackTime.ns());
EXPECT_EQ(mPeriod + timestamp, result->vsyncTime.ns());
lastTarget = timestamp;
},
"oo");
mDispatch->schedule(tmp, {.workDuration = 999, .readyDuration = 0, .lastVsync = 1000});
advanceToNextCallback();
EXPECT_THAT(lastTarget, Eq(1000));
advanceToNextCallback();
EXPECT_THAT(lastTarget, Eq(2000));
}
TEST_F(VSyncDispatchTimerQueueTest, modificationsAroundVsyncTime) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 1000)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 950)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1950)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
CountingCallback cb(mDispatch);
mDispatch->schedule(cb, {.workDuration = 0, .readyDuration = 0, .lastVsync = 1000});
mMockClock.advanceBy(750);
mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .lastVsync = 1000});
advanceToNextCallback();
mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .lastVsync = 2000});
mMockClock.advanceBy(800);
mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
}
TEST_F(VSyncDispatchTimerQueueTest, lateModifications) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 850)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1800)).InSequence(seq);
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
advanceToNextCallback();
mDispatch->schedule(cb0, {.workDuration = 200, .readyDuration = 0, .lastVsync = 2000});
mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .lastVsync = 1000});
advanceToNextCallback();
advanceToNextCallback();
}
TEST_F(VSyncDispatchTimerQueueTest, doesntCancelPriorValidTimerForFutureMod) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .lastVsync = 20000});
}
TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmCancel()).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
CountingCallback cb0(mDispatch);
mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
mDispatch->cancel(cb0);
mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
}
TEST_F(VSyncDispatchTimerQueueTest, makingUpIdsError) {
VSyncDispatch::CallbackToken token(100);
EXPECT_FALSE(
mDispatch->schedule(token, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000})
.has_value());
EXPECT_THAT(mDispatch->cancel(token), Eq(CancelResult::Error));
}
TEST_F(VSyncDispatchTimerQueueTest, canMoveCallbackBackwardsInTime) {
CountingCallback cb0(mDispatch);
auto result =
mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
result = mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(900, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
}
// b/1450138150
TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipAScheduledTargetVSync) {
SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false);
EXPECT_CALL(mMockClock, alarmAt(_, 500));
CountingCallback cb(mDispatch);
auto result =
mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
mMockClock.advanceBy(400);
result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1200, result->callbackTime.ns());
EXPECT_EQ(2000, result->vsyncTime.ns());
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
}
// b/1450138150
TEST_F(VSyncDispatchTimerQueueTest, movesCallbackBackwardsAndSkipAScheduledTargetVSync) {
SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true);
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 400)).InSequence(seq);
CountingCallback cb(mDispatch);
auto result =
mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
mMockClock.advanceBy(400);
result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(400, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
}
TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedule) {
EXPECT_CALL(*mStubTracker.get(),
nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
.Times(2)
.WillOnce(Return(1000))
.WillOnce(Return(1002));
CountingCallback cb(mDispatch);
auto result =
mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
mMockClock.advanceBy(400);
result = mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(602, result->callbackTime.ns());
EXPECT_EQ(1002, result->vsyncTime.ns());
}
TEST_F(VSyncDispatchTimerQueueTest, canScheduleNegativeOffsetAgainstDifferentPeriods) {
CountingCallback cb0(mDispatch);
auto result =
mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
advanceToNextCallback();
result =
mDispatch->schedule(cb0, {.workDuration = 1100, .readyDuration = 0, .lastVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(900, result->callbackTime.ns());
EXPECT_EQ(2000, result->vsyncTime.ns());
}
TEST_F(VSyncDispatchTimerQueueTest, canScheduleLargeNegativeOffset) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1100)).InSequence(seq);
CountingCallback cb0(mDispatch);
auto result =
mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
advanceToNextCallback();
result =
mDispatch->schedule(cb0, {.workDuration = 1900, .readyDuration = 0, .lastVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1100, result->callbackTime.ns());
EXPECT_EQ(3000, result->vsyncTime.ns());
}
TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesNotAffectSchedulingState) {
SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false);
EXPECT_CALL(mMockClock, alarmAt(_, 600));
CountingCallback cb(mDispatch);
auto result =
mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
result = mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, result->callbackTime.ns());
EXPECT_EQ(2000, result->vsyncTime.ns());
advanceToNextCallback();
}
TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesAffectSchedulingState) {
SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true);
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 0)).InSequence(seq);
CountingCallback cb(mDispatch);
auto result =
mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
result = mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(0, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
advanceToNextCallback();
}
TEST_F(VSyncDispatchTimerQueueTest, helperMove) {
EXPECT_CALL(mMockClock, alarmAt(_, 500)).Times(1);
EXPECT_CALL(mMockClock, alarmCancel()).Times(1);
VSyncCallbackRegistration cb(
mDispatch, [](auto, auto, auto) {}, "");
VSyncCallbackRegistration cb1(std::move(cb));
cb.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
cb.cancel();
cb1.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
cb1.cancel();
}
TEST_F(VSyncDispatchTimerQueueTest, helperMoveAssign) {
EXPECT_CALL(mMockClock, alarmAt(_, 500)).Times(1);
EXPECT_CALL(mMockClock, alarmCancel()).Times(1);
VSyncCallbackRegistration cb(
mDispatch, [](auto, auto, auto) {}, "");
VSyncCallbackRegistration cb1(
mDispatch, [](auto, auto, auto) {}, "");
cb1 = std::move(cb);
cb.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
cb.cancel();
cb1.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
cb1.cancel();
}
// b/154303580
TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminent) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
CountingCallback cb1(mDispatch);
CountingCallback cb2(mDispatch);
auto result =
mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
mMockClock.setLag(100);
mMockClock.advanceBy(620);
result = mDispatch->schedule(cb2, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1900, result->callbackTime.ns());
EXPECT_EQ(2000, result->vsyncTime.ns());
mMockClock.advanceBy(80);
EXPECT_THAT(cb1.mCalls.size(), Eq(1));
EXPECT_THAT(cb2.mCalls.size(), Eq(0));
}
// b/154303580.
// If the same callback tries to reschedule itself after it's too late, timer opts to apply the
// update later, as opposed to blocking the calling thread.
TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminentSameCallback) {
SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false);
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1630)).InSequence(seq);
CountingCallback cb(mDispatch);
auto result =
mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
mMockClock.setLag(100);
mMockClock.advanceBy(620);
result = mDispatch->schedule(cb, {.workDuration = 370, .readyDuration = 0, .lastVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1630, result->callbackTime.ns());
EXPECT_EQ(2000, result->vsyncTime.ns());
mMockClock.advanceBy(80);
EXPECT_THAT(cb.mCalls.size(), Eq(1));
}
// b/154303580.
// If the same callback tries to reschedule itself after it's too late, timer opts to apply the
// update later, as opposed to blocking the calling thread.
TEST_F(VSyncDispatchTimerQueueTest, doesntSkipSchedulingIfTimerReschedulingIsImminentSameCallback) {
SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true);
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1630)).InSequence(seq);
CountingCallback cb(mDispatch);
auto result =
mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
mMockClock.setLag(100);
mMockClock.advanceBy(620);
result = mDispatch->schedule(cb, {.workDuration = 370, .readyDuration = 0, .lastVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
mMockClock.advanceBy(80);
ASSERT_EQ(1, cb.mCalls.size());
EXPECT_EQ(1000, cb.mCalls[0]);
ASSERT_EQ(1, cb.mWakeupTime.size());
EXPECT_EQ(600, cb.mWakeupTime[0]);
}
// b/154303580.
TEST_F(VSyncDispatchTimerQueueTest, skipsRearmingWhenNotNextScheduled) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmCancel()).InSequence(seq);
CountingCallback cb1(mDispatch);
CountingCallback cb2(mDispatch);
auto result =
mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
result = mDispatch->schedule(cb2, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1900, result->callbackTime.ns());
EXPECT_EQ(2000, result->vsyncTime.ns());
mMockClock.setLag(100);
mMockClock.advanceBy(620);
EXPECT_EQ(mDispatch->cancel(cb2), CancelResult::Cancelled);
mMockClock.advanceBy(80);
EXPECT_THAT(cb1.mCalls.size(), Eq(1));
EXPECT_THAT(cb2.mCalls.size(), Eq(0));
}
TEST_F(VSyncDispatchTimerQueueTest, rearmsWhenCancelledAndIsNextScheduled) {
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmCancel()).InSequence(seq);
CountingCallback cb1(mDispatch);
CountingCallback cb2(mDispatch);
auto result =
mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
result = mDispatch->schedule(cb2, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1900, result->callbackTime.ns());
EXPECT_EQ(2000, result->vsyncTime.ns());
mMockClock.setLag(100);
mMockClock.advanceBy(620);
EXPECT_EQ(mDispatch->cancel(cb1), CancelResult::Cancelled);
EXPECT_THAT(cb1.mCalls.size(), Eq(0));
EXPECT_THAT(cb2.mCalls.size(), Eq(0));
mMockClock.advanceToNextCallback();
EXPECT_THAT(cb1.mCalls.size(), Eq(0));
EXPECT_THAT(cb2.mCalls.size(), Eq(1));
}
TEST_F(VSyncDispatchTimerQueueTest, laggedTimerGroupsCallbacksWithinLag) {
CountingCallback cb1(mDispatch);
CountingCallback cb2(mDispatch);
Sequence seq;
EXPECT_CALL(*mStubTracker.get(),
nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
.InSequence(seq)
.WillOnce(Return(1000));
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
EXPECT_CALL(*mStubTracker.get(),
nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
.InSequence(seq)
.WillOnce(Return(1000));
auto result =
mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
result = mDispatch->schedule(cb2, {.workDuration = 390, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(610, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
mMockClock.setLag(100);
mMockClock.advanceBy(700);
ASSERT_THAT(cb1.mWakeupTime.size(), Eq(1));
EXPECT_THAT(cb1.mWakeupTime[0], Eq(600));
ASSERT_THAT(cb1.mReadyTime.size(), Eq(1));
EXPECT_THAT(cb1.mReadyTime[0], Eq(1000));
ASSERT_THAT(cb2.mWakeupTime.size(), Eq(1));
EXPECT_THAT(cb2.mWakeupTime[0], Eq(610));
ASSERT_THAT(cb2.mReadyTime.size(), Eq(1));
EXPECT_THAT(cb2.mReadyTime[0], Eq(1000));
}
TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithReadyDuration) {
auto intended = mPeriod - 230;
EXPECT_CALL(mMockClock, alarmAt(_, 900));
CountingCallback cb(mDispatch);
const auto result =
mDispatch->schedule(cb,
{.workDuration = 70, .readyDuration = 30, .lastVsync = intended});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(900, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
EXPECT_THAT(cb.mCalls[0], Eq(mPeriod));
ASSERT_THAT(cb.mWakeupTime.size(), Eq(1));
EXPECT_THAT(cb.mWakeupTime[0], 900);
ASSERT_THAT(cb.mReadyTime.size(), Eq(1));
EXPECT_THAT(cb.mReadyTime[0], 970);
}
TEST_F(VSyncDispatchTimerQueueTest, updatesVsyncTimeForCloseWakeupTime) {
SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false);
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
CountingCallback cb(mDispatch);
mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
advanceToNextCallback();
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
EXPECT_THAT(cb.mCalls[0], Eq(2000));
ASSERT_THAT(cb.mWakeupTime.size(), Eq(1));
EXPECT_THAT(cb.mWakeupTime[0], Eq(600));
ASSERT_THAT(cb.mReadyTime.size(), Eq(1));
EXPECT_THAT(cb.mReadyTime[0], Eq(2000));
}
TEST_F(VSyncDispatchTimerQueueTest, doesNotUpdatesVsyncTimeForCloseWakeupTime) {
SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true);
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 0)).InSequence(seq);
CountingCallback cb(mDispatch);
mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
advanceToNextCallback();
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
EXPECT_THAT(cb.mCalls[0], Eq(1000));
ASSERT_THAT(cb.mWakeupTime.size(), Eq(1));
EXPECT_THAT(cb.mWakeupTime[0], Eq(0));
ASSERT_THAT(cb.mReadyTime.size(), Eq(1));
EXPECT_THAT(cb.mReadyTime[0], Eq(1000));
}
TEST_F(VSyncDispatchTimerQueueTest, skipAVsyc) {
SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false);
EXPECT_CALL(mMockClock, alarmAt(_, 500));
CountingCallback cb(mDispatch);
auto result =
mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
mMockClock.advanceBy(300);
result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1200, result->callbackTime.ns());
EXPECT_EQ(2000, result->vsyncTime.ns());
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
}
TEST_F(VSyncDispatchTimerQueueTest, dontskipAVsyc) {
SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true);
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
EXPECT_CALL(mMockClock, alarmAt(_, 300)).InSequence(seq);
CountingCallback cb(mDispatch);
auto result =
mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
mMockClock.advanceBy(300);
result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(300, result->callbackTime.ns());
EXPECT_EQ(1000, result->vsyncTime.ns());
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
EXPECT_THAT(cb.mCalls[0], Eq(1000));
ASSERT_THAT(cb.mWakeupTime.size(), Eq(1));
EXPECT_THAT(cb.mWakeupTime[0], Eq(300));
ASSERT_THAT(cb.mReadyTime.size(), Eq(1));
EXPECT_THAT(cb.mReadyTime[0], Eq(1000));
}
class VSyncDispatchTimerQueueEntryTest : public testing::Test {
protected:
nsecs_t const mPeriod = 1000;
nsecs_t const mVsyncMoveThreshold = 200;
std::shared_ptr<NiceMock<MockVSyncTracker>> mStubTracker =
std::make_shared<NiceMock<MockVSyncTracker>>(mPeriod);
};
TEST_F(VSyncDispatchTimerQueueEntryTest, stateAfterInitialization) {
std::string name("basicname");
VSyncDispatchTimerQueueEntry entry(
name, [](auto, auto, auto) {}, mVsyncMoveThreshold);
EXPECT_THAT(entry.name(), Eq(name));
EXPECT_FALSE(entry.lastExecutedVsyncTarget());
EXPECT_FALSE(entry.wakeupTime());
}
TEST_F(VSyncDispatchTimerQueueEntryTest, stateScheduling) {
VSyncDispatchTimerQueueEntry entry(
"test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
EXPECT_FALSE(entry.wakeupTime());
const auto scheduleResult =
entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0);
EXPECT_EQ(900, scheduleResult.callbackTime.ns());
EXPECT_EQ(1000, scheduleResult.vsyncTime.ns());
auto const wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
EXPECT_THAT(*wakeup, Eq(900));
entry.disarm();
EXPECT_FALSE(entry.wakeupTime());
}
TEST_F(VSyncDispatchTimerQueueEntryTest, stateSchedulingReallyLongWakeupLatency) {
auto const duration = 500;
auto const now = 8750;
EXPECT_CALL(*mStubTracker.get(),
nextAnticipatedVSyncTimeFrom(now + duration, std::optional<nsecs_t>(994)))
.Times(1)
.WillOnce(Return(10000));
VSyncDispatchTimerQueueEntry entry(
"test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
EXPECT_FALSE(entry.wakeupTime());
const auto scheduleResult =
entry.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 994},
*mStubTracker, now);
EXPECT_EQ(9500, scheduleResult.callbackTime.ns());
EXPECT_EQ(10000, scheduleResult.vsyncTime.ns());
auto const wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
EXPECT_THAT(*wakeup, Eq(9500));
}
TEST_F(VSyncDispatchTimerQueueEntryTest, runCallback) {
auto callCount = 0;
auto vsyncCalledTime = 0;
auto wakeupCalledTime = 0;
auto readyCalledTime = 0;
VSyncDispatchTimerQueueEntry entry(
"test",
[&](auto vsyncTime, auto wakeupTime, auto readyTime) {
callCount++;
vsyncCalledTime = vsyncTime;
wakeupCalledTime = wakeupTime;
readyCalledTime = readyTime;
},
mVsyncMoveThreshold);
const auto scheduleResult =
entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0);
EXPECT_EQ(900, scheduleResult.callbackTime.ns());
EXPECT_EQ(1000, scheduleResult.vsyncTime.ns());
auto const wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
EXPECT_THAT(*wakeup, Eq(900));
auto const ready = entry.readyTime();
ASSERT_TRUE(ready);
EXPECT_THAT(*ready, Eq(1000));
entry.callback(entry.executing(), *wakeup, *ready);
EXPECT_THAT(callCount, Eq(1));
EXPECT_THAT(vsyncCalledTime, Eq(mPeriod));
EXPECT_THAT(wakeupCalledTime, Eq(*wakeup));
EXPECT_FALSE(entry.wakeupTime());
auto lastCalledTarget = entry.lastExecutedVsyncTarget();
ASSERT_TRUE(lastCalledTarget);
EXPECT_THAT(*lastCalledTarget, Eq(mPeriod));
}
TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) {
EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
.Times(2)
.WillOnce(Return(1000))
.WillOnce(Return(1020));
VSyncDispatchTimerQueueEntry entry(
"test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
EXPECT_FALSE(entry.wakeupTime());
entry.update(*mStubTracker, 0);
EXPECT_FALSE(entry.wakeupTime());
const auto scheduleResult =
entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0);
EXPECT_EQ(900, scheduleResult.callbackTime.ns());
EXPECT_EQ(1000, scheduleResult.vsyncTime.ns());
auto wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
EXPECT_THAT(wakeup, Eq(900));
entry.update(*mStubTracker, 0);
wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
EXPECT_THAT(*wakeup, Eq(920));
}
TEST_F(VSyncDispatchTimerQueueEntryTest, skipsUpdateIfJustScheduled) {
VSyncDispatchTimerQueueEntry entry(
"test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
EXPECT_EQ(900,
entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
*mStubTracker.get(), 0)
.callbackTime.ns());
entry.update(*mStubTracker.get(), 0);
auto const wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
EXPECT_THAT(*wakeup, Eq(wakeup));
}
TEST_F(VSyncDispatchTimerQueueEntryTest, willSnapToNextTargettableVSync) {
VSyncDispatchTimerQueueEntry entry(
"test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
auto scheduleResult =
entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0);
EXPECT_EQ(900, scheduleResult.callbackTime.ns());
EXPECT_EQ(1000, scheduleResult.vsyncTime.ns());
entry.executing(); // 1000 is executing
// had 1000 not been executing, this could have been scheduled for time 800.
scheduleResult = entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0);
EXPECT_EQ(1800, scheduleResult.callbackTime.ns());
EXPECT_EQ(2000, scheduleResult.vsyncTime.ns());
EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
EXPECT_THAT(*entry.readyTime(), Eq(2000));
scheduleResult = entry.schedule({.workDuration = 50, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0);
EXPECT_EQ(1950, scheduleResult.callbackTime.ns());
EXPECT_EQ(2000, scheduleResult.vsyncTime.ns());
EXPECT_THAT(*entry.wakeupTime(), Eq(1950));
EXPECT_THAT(*entry.readyTime(), Eq(2000));
scheduleResult = entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 1001},
*mStubTracker, 0);
EXPECT_EQ(1800, scheduleResult.callbackTime.ns());
EXPECT_EQ(2000, scheduleResult.vsyncTime.ns());
EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
EXPECT_THAT(*entry.readyTime(), Eq(2000));
}
TEST_F(VSyncDispatchTimerQueueEntryTest,
willRequestNextEstimateWhenSnappingToNextTargettableVSync) {
VSyncDispatchTimerQueueEntry entry(
"test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
Sequence seq;
EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500, std::optional<nsecs_t>(500)))
.InSequence(seq)
.WillOnce(Return(1000));
EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500, std::optional<nsecs_t>(500)))
.InSequence(seq)
.WillOnce(Return(1000));
EXPECT_CALL(*mStubTracker.get(),
nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold,
std::optional<nsecs_t>(1000)))
.InSequence(seq)
.WillOnce(Return(2000));
auto scheduleResult =
entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0);
EXPECT_EQ(900, scheduleResult.callbackTime.ns());
EXPECT_EQ(1000, scheduleResult.vsyncTime.ns());
entry.executing(); // 1000 is executing
scheduleResult = entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0);
EXPECT_EQ(1800, scheduleResult.callbackTime.ns());
EXPECT_EQ(2000, scheduleResult.vsyncTime.ns());
}
TEST_F(VSyncDispatchTimerQueueEntryTest, reportsScheduledIfStillTime) {
VSyncDispatchTimerQueueEntry entry("test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
EXPECT_EQ(900,
entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0)
.callbackTime.ns());
EXPECT_EQ(800,
entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0)
.callbackTime.ns());
EXPECT_EQ(950,
entry.schedule({.workDuration = 50, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0)
.callbackTime.ns());
{
SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, true);
EXPECT_EQ(0,
entry.schedule({.workDuration = 1200, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0)
.callbackTime.ns());
}
{
SET_FLAG_FOR_TEST(flags::dont_skip_on_early_ro, false);
EXPECT_EQ(800,
entry.schedule({.workDuration = 1200, .readyDuration = 0, .lastVsync = 500},
*mStubTracker, 0)
.callbackTime.ns());
}
}
TEST_F(VSyncDispatchTimerQueueEntryTest, storesPendingUpdatesUntilUpdateAndDontSkip) {
static constexpr auto effectualOffset = 200;
VSyncDispatchTimerQueueEntry entry(
"test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
EXPECT_FALSE(entry.hasPendingWorkloadUpdate());
entry.addPendingWorkloadUpdate(*mStubTracker.get(), 0,
{.workDuration = 100, .readyDuration = 0, .lastVsync = 400});
entry.addPendingWorkloadUpdate(*mStubTracker.get(), 0,
{.workDuration = effectualOffset,
.readyDuration = 0,
.lastVsync = 400});
EXPECT_TRUE(entry.hasPendingWorkloadUpdate());
entry.update(*mStubTracker, 0);
EXPECT_FALSE(entry.hasPendingWorkloadUpdate());
EXPECT_THAT(*entry.wakeupTime(), Eq(mPeriod - effectualOffset));
}
TEST_F(VSyncDispatchTimerQueueEntryTest, runCallbackWithReadyDuration) {
auto callCount = 0;
auto vsyncCalledTime = 0;
auto wakeupCalledTime = 0;
auto readyCalledTime = 0;
VSyncDispatchTimerQueueEntry entry(
"test",
[&](auto vsyncTime, auto wakeupTime, auto readyTime) {
callCount++;
vsyncCalledTime = vsyncTime;
wakeupCalledTime = wakeupTime;
readyCalledTime = readyTime;
},
mVsyncMoveThreshold);
const auto scheduleResult =
entry.schedule({.workDuration = 70, .readyDuration = 30, .lastVsync = 500},
*mStubTracker, 0);
EXPECT_EQ(900, scheduleResult.callbackTime.ns());
EXPECT_EQ(mPeriod, scheduleResult.vsyncTime.ns());
auto const wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
EXPECT_THAT(*wakeup, Eq(900));
auto const ready = entry.readyTime();
ASSERT_TRUE(ready);
EXPECT_THAT(*ready, Eq(970));
entry.callback(entry.executing(), *wakeup, *ready);
EXPECT_THAT(callCount, Eq(1));
EXPECT_THAT(vsyncCalledTime, Eq(mPeriod));
EXPECT_THAT(wakeupCalledTime, Eq(*wakeup));
EXPECT_FALSE(entry.wakeupTime());
auto lastCalledTarget = entry.lastExecutedVsyncTarget();
ASSERT_TRUE(lastCalledTarget);
EXPECT_THAT(*lastCalledTarget, Eq(mPeriod));
}
} // namespace android::scheduler
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"