1
0
Fork 0

drm_hwcomposer: Add tests for FlatteningController

Add gtest unittests to validate expected behaviour of
FlatteningController.

Change-Id: Ia40d232c7a72912df9480bd82adfe3315baaa493
This commit is contained in:
Drew Davenport 2025-09-16 14:54:14 -06:00
parent 2432cb0dfa
commit c50f6851d6
3 changed files with 203 additions and 1 deletions

View file

@ -97,7 +97,10 @@ clean:
# Build
# clang-tidy picks up gtest and gmock headers
# IStats includes are not available in aospless
SKIP_FILES := stats/CompositionStatsTest.cpp stats/CompositionStatsAtomReporterDesktop.cpp
SKIP_FILES := \
compositor/FlatteningControllerTests.cpp \
stats/CompositionStatsTest.cpp \
stats/CompositionStatsAtomReporterDesktop.cpp \
BUILD_FILES_AUTO := $(shell find -L $(SRC_DIR) -not -path '*/\.*' -not -path '*/tests/test_include/*' -path '*.cpp')
SKIP_FILES_path := $(foreach file,$(SKIP_FILES),$(SRC_DIR)/$(file))

View file

@ -274,10 +274,13 @@ apex {
cc_test_host {
name: "drm_hwcomposer_unittests",
srcs: [
"compositor/FlatteningController.cpp",
"compositor/FlatteningControllerTests.cpp",
"stats/CompositionStats.cpp",
"stats/CompositionStatsTest.cpp",
],
static_libs: [
"libbase",
"libgmock",
"libgtest",
],

View file

@ -0,0 +1,196 @@
/*
* Copyright (C) 2025 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.
*/
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <thread>
#include "compositor/FlatteningController.h"
using ::testing::StrictMock;
namespace android::drm_hwcomposer {
namespace {
constexpr auto kTestTimeout = std::chrono::milliseconds(100);
constexpr auto kTimeoutEpsilon = std::chrono::milliseconds(50);
} // namespace
class MockFlatConCallbacks {
public:
MOCK_METHOD(void, Trigger, ());
};
class FlatteningControllerTest : public ::testing::Test {
protected:
static void EmptyTrigger() {
}
};
TEST_F(FlatteningControllerTest, DisabledOnCreation) {
FlatConCallbacks cbks = {.trigger = EmptyTrigger};
auto flat_con = FlatteningController::CreateInstance(cbks, kTestTimeout);
EXPECT_FALSE(flat_con->ShouldFlatten());
std::this_thread::sleep_for(kTestTimeout + kTimeoutEpsilon);
EXPECT_FALSE(flat_con->ShouldFlatten());
}
TEST_F(FlatteningControllerTest, EnabledAfterNewFrame) {
FlatConCallbacks cbks = {.trigger = EmptyTrigger};
auto flat_con = FlatteningController::CreateInstance(cbks, kTestTimeout);
flat_con->NewFrame();
EXPECT_FALSE(flat_con->ShouldFlatten());
std::this_thread::sleep_for(kTestTimeout + kTimeoutEpsilon);
EXPECT_TRUE(flat_con->ShouldFlatten());
}
TEST_F(FlatteningControllerTest, DisabledAfterCallingDisable) {
FlatConCallbacks cbks = {.trigger = EmptyTrigger};
auto flat_con = FlatteningController::CreateInstance(cbks, kTestTimeout);
flat_con->NewFrame();
std::this_thread::sleep_for(kTestTimeout + kTimeoutEpsilon);
EXPECT_TRUE(flat_con->ShouldFlatten());
flat_con->Disable();
EXPECT_FALSE(flat_con->ShouldFlatten());
std::this_thread::sleep_for(kTestTimeout + kTimeoutEpsilon);
EXPECT_FALSE(flat_con->ShouldFlatten());
}
TEST_F(FlatteningControllerTest, TriggersCallbackAfterTimeout) {
StrictMock<MockFlatConCallbacks> mock_cb;
FlatConCallbacks cbks = {.trigger = [&]() { mock_cb.Trigger(); }};
auto flat_con = FlatteningController::CreateInstance(cbks, kTestTimeout);
EXPECT_CALL(mock_cb, Trigger()).Times(1);
flat_con->NewFrame();
std::this_thread::sleep_for(kTestTimeout + kTimeoutEpsilon);
}
TEST_F(FlatteningControllerTest, ShouldFlattenAfterFirstNewFrame) {
StrictMock<MockFlatConCallbacks> mock_cb;
FlatConCallbacks cbks = {.trigger = [&]() { mock_cb.Trigger(); }};
auto flat_con = FlatteningController::CreateInstance(cbks, kTestTimeout);
EXPECT_CALL(mock_cb, Trigger()).Times(1);
// Enable the controller and start the timer.
flat_con->NewFrame();
std::this_thread::sleep_for(kTestTimeout + kTimeoutEpsilon);
EXPECT_TRUE(flat_con->ShouldFlatten());
// First NewFrame is in response to the refresh callback.
flat_con->NewFrame();
EXPECT_TRUE(flat_con->ShouldFlatten());
}
TEST_F(FlatteningControllerTest, ShouldNotFlattenAfterSecondNewFrame) {
StrictMock<MockFlatConCallbacks> mock_cb;
FlatConCallbacks cbks = {.trigger = [&]() { mock_cb.Trigger(); }};
auto flat_con = FlatteningController::CreateInstance(cbks, kTestTimeout);
EXPECT_CALL(mock_cb, Trigger()).Times(1);
// Enable the controller and start the timer.
flat_con->NewFrame();
std::this_thread::sleep_for(kTestTimeout + kTimeoutEpsilon);
EXPECT_TRUE(flat_con->ShouldFlatten());
// First NewFrame is in response to the refresh callback, should stay
// flattened.
flat_con->NewFrame();
EXPECT_TRUE(flat_con->ShouldFlatten());
// Second NewFrame should not flatten.
flat_con->NewFrame();
EXPECT_FALSE(flat_con->ShouldFlatten());
}
TEST_F(FlatteningControllerTest, TriggersCallbackAtCorrectTime) {
std::mutex mutex;
std::condition_variable cv;
bool triggered = false;
std::chrono::steady_clock::time_point start_time;
std::chrono::steady_clock::time_point trigger_time;
FlatConCallbacks cbks = {.trigger = [&]() {
std::lock_guard<std::mutex> lock(mutex);
trigger_time = std::chrono::steady_clock::now();
triggered = true;
cv.notify_one();
}};
auto flat_con = FlatteningController::CreateInstance(cbks, kTestTimeout);
start_time = std::chrono::steady_clock::now();
flat_con->NewFrame();
std::unique_lock<std::mutex> lock(mutex);
cv.wait_for(lock, kTestTimeout * 2, [&] { return triggered; });
EXPECT_TRUE(triggered);
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
trigger_time - start_time);
EXPECT_GE(duration, kTestTimeout);
EXPECT_LT(duration, kTestTimeout + kTimeoutEpsilon);
}
TEST_F(FlatteningControllerTest, ResetsTimeoutOnNewFrame) {
std::mutex mutex;
std::condition_variable cv;
bool triggered = false;
std::chrono::steady_clock::time_point start_time;
std::chrono::steady_clock::time_point trigger_time;
FlatConCallbacks cbks = {.trigger = [&]() {
std::lock_guard<std::mutex> lock(mutex);
trigger_time = std::chrono::steady_clock::now();
triggered = true;
cv.notify_one();
}};
auto flat_con = FlatteningController::CreateInstance(cbks, kTestTimeout);
flat_con->NewFrame();
std::this_thread::sleep_for(kTestTimeout / 2);
// NewFrame is called before the original timeout has expired.
start_time = std::chrono::steady_clock::now();
flat_con->NewFrame();
std::unique_lock<std::mutex> lock(mutex);
cv.wait_for(lock, kTestTimeout * 2, [&] { return triggered; });
EXPECT_TRUE(triggered);
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
trigger_time - start_time);
EXPECT_GE(duration, kTestTimeout);
EXPECT_LT(duration, kTestTimeout + kTimeoutEpsilon);
}
} // namespace android::drm_hwcomposer