/*
* Copyright (C) 2016 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 <general_test/timer_stress_test.h>
#include <cinttypes>
#include <cstddef>
#include <shared/send_message.h>
#include <chre.h>
using nanoapp_testing::sendFatalFailureToHost;
using nanoapp_testing::sendInternalFailureToHost;
using nanoapp_testing::sendSuccessToHost;
/*
* We stress the system by setting more and more timers until the system
* runs out. We then cancel one (CT) and set a new timer post-cancel (NT).
* We make sure all the timers we set fire.
*
* Our stages are:
* Stage 0: Successfully cancel CT.
* Stage 1: All of our "exhaustion" timers fire.
* Stage 2: The new timer, NT, fires.
*
* After all of our stages have succeeded, we send success to the host. Note
* there is no system requirement that Stage 2 happens after Stage 1, so
* we use markSuccess() to track this.
*/
// Allow 1000ms to create the large number of timers specified below. This
// equates to approximately 1ms per timer which should give ample time for
// timer creation to complete.
constexpr uint64_t kDuration = UINT64_C(1000000000);
// If the system keeps claiming it can set more timers, we don't let it
// continue forever. Instead, we'll cut it off at this limit. And then
// we'll call its bluff, and make sure that all of these timers legitimately
// fire. While it won't be an actual exhaustion test (we never took the
// system down to no more timers available), it will still give us confidence
// that this CHRE can properly handle any semi-reasonable timer load properly.
// 1030 is an arbitrary number, slightly over 2^10. The hope is this
// balances between catching incorrect behavior and the test taking too long.
constexpr int32_t kMaxTimersToSet = INT32_C(1030);
namespace general_test {
namespace {
const uint32_t kCookies[] = {0, 1, 2};
} // anonymous namespace
void TimerStressTest::startStages() {
uint32_t cancelId = chreTimerSet(kDuration, &kCookies[0], true);
if (cancelId == CHRE_TIMER_INVALID) {
sendFatalFailureToHost("No timers available");
}
mStage1CallbacksLeft = 0;
// We anticipate most CHREs will not reach kMaxTimersToSet.
while (mStage1CallbacksLeft < kMaxTimersToSet) {
if (chreTimerSet(kDuration, &kCookies[1], true) == CHRE_TIMER_INVALID) {
break;
}
mStage1CallbacksLeft++;
}
if (mStage1CallbacksLeft == 0) {
sendFatalFailureToHost("Insufficient timers available");
}
if (!chreTimerCancel(cancelId)) {
sendFatalFailureToHost("Unable to cancel timer");
}
markSuccess(0);
if (chreTimerSet(kDuration, &kCookies[2], true) == CHRE_TIMER_INVALID) {
sendFatalFailureToHost("Unable to set new timer after successful "
"cancel.");
}
}
TimerStressTest::TimerStressTest()
: Test(CHRE_API_VERSION_1_0),
mInMethod(false),
mFinishedBitmask(0),
mStage1CallbacksLeft(0) {
}
void TimerStressTest::setUp(uint32_t messageSize, const void * /* message */) {
mInMethod = true;
if (messageSize != 0) {
sendFatalFailureToHost(
"TimerStress message expects 0 additional bytes, got ",
&messageSize);
}
startStages();
mInMethod = false;
}
void TimerStressTest::handleStageEvent(uint32_t index) {
switch (index) {
case 0:
sendFatalFailureToHost("Canceled timer fired:", &index);
break;
case 1:
--mStage1CallbacksLeft;
if (mStage1CallbacksLeft <= 0) {
markSuccess(index);
}
break;
case 2:
markSuccess(index);
break;
default:
sendFatalFailureToHost("Unexpected event stage:", &index);
}
}
void TimerStressTest::handleEvent(uint32_t senderInstanceId,
uint16_t eventType, const void* eventData) {
if (mInMethod) {
sendFatalFailureToHost("handleEvent invoked while another nanoapp "
"method is running");
}
mInMethod = true;
if (senderInstanceId != CHRE_INSTANCE_ID) {
sendFatalFailureToHost("handleEvent got event from unexpected sender:",
&senderInstanceId);
}
if (eventType != CHRE_EVENT_TIMER) {
unexpectedEvent(eventType);
}
const uint32_t *data = static_cast<const uint32_t *>(eventData);
handleStageEvent(*data);
mInMethod = false;
}
void TimerStressTest::markSuccess(uint32_t stage) {
chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
uint32_t finishedBit = (1 << stage);
if ((kAllFinished & finishedBit) == 0) {
sendFatalFailureToHost("markSuccess bad stage:", &stage);
}
if ((mFinishedBitmask & finishedBit) != 0) {
sendFatalFailureToHost("timer over-triggered:", &stage);
}
mFinishedBitmask |= finishedBit;
if (mFinishedBitmask == kAllFinished) {
sendSuccessToHost();
}
}
} // namespace general_test