1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <gmock/gmock.h>
18 #include <gtest/gtest.h>
19 #include <private/performance_hint_private.h>
20 #include <renderthread/HintSessionWrapper.h>
21 #include <utils/Log.h>
22
23 #include <chrono>
24
25 #include "Properties.h"
26 #include "tests/common/TestUtils.h"
27
28 using namespace testing;
29 using namespace std::chrono_literals;
30 using namespace android::uirenderer::renderthread;
31
32 APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123);
33 APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456);
34 int uiThreadId = 1;
35 int renderThreadId = 2;
36
37 namespace android::uirenderer::renderthread {
38
39 class HintSessionWrapperTests : public testing::Test {
40 public:
41 void SetUp() override;
42 void TearDown() override;
43
44 protected:
45 std::shared_ptr<HintSessionWrapper> mWrapper;
46
47 std::promise<int> blockDestroyCallUntil;
48 std::promise<int> waitForDestroyFinished;
49
50 class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding {
51 public:
52 void init() override;
53
54 MOCK_METHOD(APerformanceHintManager*, fakeGetManager, ());
55 MOCK_METHOD(APerformanceHintSession*, fakeCreateSessionInternal,
56 (APerformanceHintManager*, const int32_t*, size_t, int64_t, SessionTag));
57 MOCK_METHOD(void, fakeCloseSession, (APerformanceHintSession*));
58 MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t));
59 MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t));
60 MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t));
61 MOCK_METHOD(int, fakeSetThreads, (APerformanceHintSession*, const std::vector<pid_t>&));
62 // Needs to be on the binding so it can be accessed from static methods
63 std::promise<int> allowCreationToFinish;
64 };
65
66 // Must be static so it can have function pointers we can point to with static methods
67 static std::shared_ptr<MockHintSessionBinding> sMockBinding;
68
allowCreationToFinish()69 static void allowCreationToFinish() { sMockBinding->allowCreationToFinish.set_value(1); }
allowDelayedDestructionToStart()70 void allowDelayedDestructionToStart() { blockDestroyCallUntil.set_value(1); }
waitForDelayedDestructionToFinish()71 void waitForDelayedDestructionToFinish() { waitForDestroyFinished.get_future().wait(); }
72
73 // Must be static so we can point to them as normal fn pointers with HintSessionBinding
stubGetManager()74 static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); };
stubCreateSessionInternal(APerformanceHintManager * manager,const int32_t * ids,size_t idsSize,int64_t initialTarget,SessionTag tag)75 static APerformanceHintSession* stubCreateSessionInternal(APerformanceHintManager* manager,
76 const int32_t* ids, size_t idsSize,
77 int64_t initialTarget,
78 SessionTag tag) {
79 return sMockBinding->fakeCreateSessionInternal(manager, ids, idsSize, initialTarget,
80 SessionTag::HWUI);
81 }
stubManagedCreateSessionInternal(APerformanceHintManager * manager,const int32_t * ids,size_t idsSize,int64_t initialTarget,SessionTag tag)82 static APerformanceHintSession* stubManagedCreateSessionInternal(
83 APerformanceHintManager* manager, const int32_t* ids, size_t idsSize,
84 int64_t initialTarget, SessionTag tag) {
85 sMockBinding->allowCreationToFinish.get_future().wait();
86 return sMockBinding->fakeCreateSessionInternal(manager, ids, idsSize, initialTarget,
87 SessionTag::HWUI);
88 }
stubSlowCreateSessionInternal(APerformanceHintManager * manager,const int32_t * ids,size_t idsSize,int64_t initialTarget,SessionTag tag)89 static APerformanceHintSession* stubSlowCreateSessionInternal(APerformanceHintManager* manager,
90 const int32_t* ids,
91 size_t idsSize,
92 int64_t initialTarget,
93 SessionTag tag) {
94 std::this_thread::sleep_for(50ms);
95 return sMockBinding->fakeCreateSessionInternal(manager, ids, idsSize, initialTarget,
96 SessionTag::HWUI);
97 }
stubCloseSession(APerformanceHintSession * session)98 static void stubCloseSession(APerformanceHintSession* session) {
99 sMockBinding->fakeCloseSession(session);
100 };
stubUpdateTargetWorkDuration(APerformanceHintSession * session,int64_t workDuration)101 static void stubUpdateTargetWorkDuration(APerformanceHintSession* session,
102 int64_t workDuration) {
103 sMockBinding->fakeUpdateTargetWorkDuration(session, workDuration);
104 }
stubReportActualWorkDuration(APerformanceHintSession * session,int64_t workDuration)105 static void stubReportActualWorkDuration(APerformanceHintSession* session,
106 int64_t workDuration) {
107 sMockBinding->fakeReportActualWorkDuration(session, workDuration);
108 }
stubSendHint(APerformanceHintSession * session,int32_t hintId)109 static void stubSendHint(APerformanceHintSession* session, int32_t hintId) {
110 sMockBinding->fakeSendHint(session, hintId);
111 };
stubSetThreads(APerformanceHintSession * session,const pid_t * ids,size_t size)112 static int stubSetThreads(APerformanceHintSession* session, const pid_t* ids, size_t size) {
113 std::vector<pid_t> tids(ids, ids + size);
114 return sMockBinding->fakeSetThreads(session, tids);
115 }
waitForWrapperReady()116 void waitForWrapperReady() {
117 if (mWrapper->mHintSessionFuture.has_value()) {
118 mWrapper->mHintSessionFuture->wait();
119 }
120 }
waitForSetThreadsReady()121 void waitForSetThreadsReady() {
122 if (mWrapper->mSetThreadsFuture.has_value()) {
123 mWrapper->mSetThreadsFuture->wait();
124 }
125 }
scheduleDelayedDestroyManaged()126 void scheduleDelayedDestroyManaged() {
127 TestUtils::runOnRenderThread([&](renderthread::RenderThread& rt) {
128 // Guaranteed to be scheduled first, allows destruction to start
129 rt.queue().postDelayed(0_ms, [&] { blockDestroyCallUntil.get_future().wait(); });
130 // Guaranteed to be scheduled second, destroys the session
131 mWrapper->delayedDestroy(rt, 1_ms, mWrapper);
132 // This is guaranteed to be queued after the destroy, signals that destruction is done
133 rt.queue().postDelayed(1_ms, [&] { waitForDestroyFinished.set_value(1); });
134 });
135 }
136 };
137
138 std::shared_ptr<HintSessionWrapperTests::MockHintSessionBinding>
139 HintSessionWrapperTests::sMockBinding;
140
SetUp()141 void HintSessionWrapperTests::SetUp() {
142 // Pretend it's supported even if we're in an emulator
143 Properties::useHintManager = true;
144 sMockBinding = std::make_shared<NiceMock<MockHintSessionBinding>>();
145 mWrapper = std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId);
146 mWrapper->mBinding = sMockBinding;
147 EXPECT_CALL(*sMockBinding, fakeGetManager).WillOnce(Return(managerPtr));
148 ON_CALL(*sMockBinding, fakeCreateSessionInternal).WillByDefault(Return(sessionPtr));
149 ON_CALL(*sMockBinding, fakeSetThreads).WillByDefault(Return(0));
150 }
151
init()152 void HintSessionWrapperTests::MockHintSessionBinding::init() {
153 sMockBinding->getManager = &stubGetManager;
154 if (sMockBinding->createSessionInternal == nullptr) {
155 sMockBinding->createSessionInternal = &stubCreateSessionInternal;
156 }
157 sMockBinding->closeSession = &stubCloseSession;
158 sMockBinding->updateTargetWorkDuration = &stubUpdateTargetWorkDuration;
159 sMockBinding->reportActualWorkDuration = &stubReportActualWorkDuration;
160 sMockBinding->sendHint = &stubSendHint;
161 sMockBinding->setThreads = &stubSetThreads;
162 }
163
TearDown()164 void HintSessionWrapperTests::TearDown() {
165 // Ensure that anything running on RT is completely finished
166 mWrapper = nullptr;
167 sMockBinding = nullptr;
168 }
169
TEST_F(HintSessionWrapperTests,destructorClosesBackgroundSession)170 TEST_F(HintSessionWrapperTests, destructorClosesBackgroundSession) {
171 EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
172 sMockBinding->createSessionInternal = stubSlowCreateSessionInternal;
173 mWrapper->init();
174 mWrapper = nullptr;
175 Mock::VerifyAndClearExpectations(sMockBinding.get());
176 }
177
TEST_F(HintSessionWrapperTests,sessionInitializesCorrectly)178 TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) {
179 EXPECT_CALL(*sMockBinding, fakeCreateSessionInternal(managerPtr, _, Gt(1), _, _)).Times(1);
180 mWrapper->init();
181 waitForWrapperReady();
182 }
183
TEST_F(HintSessionWrapperTests,loadUpHintsSendCorrectly)184 TEST_F(HintSessionWrapperTests, loadUpHintsSendCorrectly) {
185 EXPECT_CALL(*sMockBinding,
186 fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
187 .Times(1);
188 mWrapper->init();
189 waitForWrapperReady();
190 mWrapper->sendLoadIncreaseHint();
191 }
192
TEST_F(HintSessionWrapperTests,loadResetHintsSendCorrectly)193 TEST_F(HintSessionWrapperTests, loadResetHintsSendCorrectly) {
194 EXPECT_CALL(*sMockBinding,
195 fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_RESET)))
196 .Times(1);
197 mWrapper->init();
198 waitForWrapperReady();
199 mWrapper->sendLoadResetHint();
200 }
201
TEST_F(HintSessionWrapperTests,delayedDeletionWorksCorrectlyAndOnlyClosesOnce)202 TEST_F(HintSessionWrapperTests, delayedDeletionWorksCorrectlyAndOnlyClosesOnce) {
203 EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
204 mWrapper->init();
205 waitForWrapperReady();
206 // Init a second time just to ensure the wrapper grabs the promise value
207 mWrapper->init();
208
209 EXPECT_EQ(mWrapper->alive(), true);
210
211 // Schedule delayed destruction, allow it to run, and check when it's done
212 scheduleDelayedDestroyManaged();
213 allowDelayedDestructionToStart();
214 waitForDelayedDestructionToFinish();
215
216 // Ensure it closed within the timeframe of the test
217 Mock::VerifyAndClearExpectations(sMockBinding.get());
218 EXPECT_EQ(mWrapper->alive(), false);
219 // If we then delete the wrapper, it shouldn't close the session again
220 EXPECT_CALL(*sMockBinding, fakeCloseSession(_)).Times(0);
221 mWrapper = nullptr;
222 }
223
TEST_F(HintSessionWrapperTests,delayedDeletionResolvesBeforeAsyncCreationFinishes)224 TEST_F(HintSessionWrapperTests, delayedDeletionResolvesBeforeAsyncCreationFinishes) {
225 // Here we test whether queueing delayedDestroy works while creation is still happening, if
226 // creation happens after
227 EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
228 sMockBinding->createSessionInternal = &stubManagedCreateSessionInternal;
229
230 // Start creating the session and destroying it at the same time
231 mWrapper->init();
232 scheduleDelayedDestroyManaged();
233
234 // Allow destruction to happen first
235 allowDelayedDestructionToStart();
236
237 // Make sure destruction has had time to happen
238 std::this_thread::sleep_for(50ms);
239
240 // Then, allow creation to finish after delayed destroy runs
241 allowCreationToFinish();
242
243 // Wait for destruction to finish
244 waitForDelayedDestructionToFinish();
245
246 // Ensure it closed within the timeframe of the test
247 Mock::VerifyAndClearExpectations(sMockBinding.get());
248 EXPECT_EQ(mWrapper->alive(), false);
249 }
250
TEST_F(HintSessionWrapperTests,delayedDeletionResolvesAfterAsyncCreationFinishes)251 TEST_F(HintSessionWrapperTests, delayedDeletionResolvesAfterAsyncCreationFinishes) {
252 // Here we test whether queueing delayedDestroy works while creation is still happening, if
253 // creation happens before
254 EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
255 sMockBinding->createSessionInternal = &stubManagedCreateSessionInternal;
256
257 // Start creating the session and destroying it at the same time
258 mWrapper->init();
259 scheduleDelayedDestroyManaged();
260
261 // Allow creation to happen first
262 allowCreationToFinish();
263
264 // Make sure creation has had time to happen
265 waitForWrapperReady();
266
267 // Then allow destruction to happen after creation is done
268 allowDelayedDestructionToStart();
269
270 // Wait for it to finish
271 waitForDelayedDestructionToFinish();
272
273 // Ensure it closed within the timeframe of the test
274 Mock::VerifyAndClearExpectations(sMockBinding.get());
275 EXPECT_EQ(mWrapper->alive(), false);
276 }
277
TEST_F(HintSessionWrapperTests,delayedDeletionDoesNotKillReusedSession)278 TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
279 EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
280 EXPECT_CALL(*sMockBinding, fakeReportActualWorkDuration(sessionPtr, 5_ms)).Times(1);
281
282 mWrapper->init();
283 waitForWrapperReady();
284 // Init a second time just to grab the wrapper from the promise
285 mWrapper->init();
286 EXPECT_EQ(mWrapper->alive(), true);
287
288 // First schedule the deletion
289 scheduleDelayedDestroyManaged();
290
291 // Then, report an actual duration
292 mWrapper->reportActualWorkDuration(5_ms);
293
294 // Then, run the delayed deletion after sending the update
295 allowDelayedDestructionToStart();
296 waitForDelayedDestructionToFinish();
297
298 // Ensure it didn't close within the timeframe of the test
299 Mock::VerifyAndClearExpectations(sMockBinding.get());
300 EXPECT_EQ(mWrapper->alive(), true);
301 }
302
TEST_F(HintSessionWrapperTests,loadUpDoesNotResetDeletionTimer)303 TEST_F(HintSessionWrapperTests, loadUpDoesNotResetDeletionTimer) {
304 EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
305 EXPECT_CALL(*sMockBinding,
306 fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
307 .Times(1);
308
309 mWrapper->init();
310 waitForWrapperReady();
311 // Init a second time just to grab the wrapper from the promise
312 mWrapper->init();
313 EXPECT_EQ(mWrapper->alive(), true);
314
315 // First schedule the deletion
316 scheduleDelayedDestroyManaged();
317
318 // Then, send a load_up hint
319 mWrapper->sendLoadIncreaseHint();
320
321 // Then, run the delayed deletion after sending the update
322 allowDelayedDestructionToStart();
323 waitForDelayedDestructionToFinish();
324
325 // Ensure it closed within the timeframe of the test
326 Mock::VerifyAndClearExpectations(sMockBinding.get());
327 EXPECT_EQ(mWrapper->alive(), false);
328 }
329
TEST_F(HintSessionWrapperTests,manualSessionDestroyPlaysNiceWithDelayedDestruct)330 TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct) {
331 EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
332
333 mWrapper->init();
334 waitForWrapperReady();
335 // Init a second time just to grab the wrapper from the promise
336 mWrapper->init();
337 EXPECT_EQ(mWrapper->alive(), true);
338
339 // First schedule the deletion
340 scheduleDelayedDestroyManaged();
341
342 // Then, kill the session
343 mWrapper->destroy();
344
345 // Verify it died
346 Mock::VerifyAndClearExpectations(sMockBinding.get());
347 EXPECT_EQ(mWrapper->alive(), false);
348
349 EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
350
351 // Then, run the delayed deletion after manually killing the session
352 allowDelayedDestructionToStart();
353 waitForDelayedDestructionToFinish();
354
355 // Ensure it didn't close again and is still dead
356 Mock::VerifyAndClearExpectations(sMockBinding.get());
357 EXPECT_EQ(mWrapper->alive(), false);
358 }
359
TEST_F(HintSessionWrapperTests,setThreadsUpdatesSessionThreads)360 TEST_F(HintSessionWrapperTests, setThreadsUpdatesSessionThreads) {
361 EXPECT_CALL(*sMockBinding, fakeCreateSessionInternal(managerPtr, _, Gt(1), _, _)).Times(1);
362 EXPECT_CALL(*sMockBinding, fakeSetThreads(sessionPtr, testing::IsSupersetOf({11, 22})))
363 .Times(1);
364 mWrapper->init();
365 waitForWrapperReady();
366
367 // This changes the overall set of threads in the session, so the session wrapper should call
368 // setThreads.
369 mWrapper->setActiveFunctorThreads({11, 22});
370 waitForSetThreadsReady();
371
372 // The set of threads doesn't change, so the session wrapper should not call setThreads this
373 // time. The order of the threads shouldn't matter.
374 mWrapper->setActiveFunctorThreads({22, 11});
375 waitForSetThreadsReady();
376 }
377
TEST_F(HintSessionWrapperTests,setThreadsDoesntCrashAfterDestroy)378 TEST_F(HintSessionWrapperTests, setThreadsDoesntCrashAfterDestroy) {
379 EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
380
381 mWrapper->init();
382 waitForWrapperReady();
383 // Init a second time just to grab the wrapper from the promise
384 mWrapper->init();
385 EXPECT_EQ(mWrapper->alive(), true);
386
387 // Then, kill the session
388 mWrapper->destroy();
389
390 // Verify it died
391 Mock::VerifyAndClearExpectations(sMockBinding.get());
392 EXPECT_EQ(mWrapper->alive(), false);
393
394 // setActiveFunctorThreads shouldn't do anything, and shouldn't crash.
395 EXPECT_CALL(*sMockBinding, fakeSetThreads(_, _)).Times(0);
396 mWrapper->setActiveFunctorThreads({11, 22});
397 waitForSetThreadsReady();
398 }
399
400 } // namespace android::uirenderer::renderthread