1/*
2 * Copyright (C) 2016 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 <general_test/timer_set_test.h>
18
19#include <cinttypes>
20#include <cstddef>
21#include <new>
22
23#include <shared/send_message.h>
24
25#include <chre.h>
26
27using nanoapp_testing::sendFatalFailureToHost;
28using nanoapp_testing::sendInternalFailureToHost;
29using nanoapp_testing::sendSuccessToHost;
30
31/*
32 * We have various "stages" for different timer setups we want to test.
33 * To speed up the test, we run all our stages simultaneously.  That
34 * requires having 6 timers available, but with a 32 timer minimum
35 * and a presumption that tests aren't going to be run alongside a lot
36 * of other nanoapps, this should be fine.
37 *
38 * See initStages() for the description of each stage.  Since these all
39 * happen in parallel, we leave it to each stage to mark itself has having
40 * succeeded, and have markSuccess() tell the Host when all stages have
41 * reported in.
42 *
43 * Note that we intentionally place the one-shot timers first, to give
44 * us more time to notice them (incorrectly) firing multiple times.
45 */
46
47// 10 milliseconds
48static uint64_t kShortDuration = UINT64_C(10000000);
49// 1 second
50static uint64_t kOneSecond = UINT64_C(1000000000);
51static uint64_t kLongDuration = kOneSecond;
52
53namespace general_test {
54
55TimerSetTest::Stage::Stage(uint32_t stage, uint64_t duration,
56                           const void *cookie, bool oneShot)
57    : mSetTime(0), mDuration(duration), mStage(stage), mEventCount(0),
58      mCookie(cookie), mOneShot(oneShot) {}
59
60void TimerSetTest::Stage::start() {
61  mSetTime = chreGetTime();
62  mTimerHandle = chreTimerSet(mDuration, mCookie, mOneShot);
63  if (mTimerHandle == CHRE_TIMER_INVALID) {
64    sendFatalFailureToHost("Unable to set timer ", &mStage);
65  }
66  if (mSetTime == 0) {
67    sendFatalFailureToHost("chreGetTime() gave 0");
68  }
69}
70
71void TimerSetTest::Stage::processEvent(uint64_t timestamp, TimerSetTest *test) {
72  if (mSetTime == 0) {
73    sendInternalFailureToHost("Didn't initialize mSetTime");
74  }
75  mEventCount++;
76
77  uint64_t expectedTime = mSetTime + (mEventCount * mDuration);
78  if (timestamp < expectedTime) {
79    sendFatalFailureToHost("Timer triggered too soon ", &mStage);
80  }
81  // TODO(b/32179037): Make this check much stricter.
82  if (timestamp > (expectedTime + kOneSecond)) {
83    sendFatalFailureToHost("Timer triggered over a second late ", &mStage);
84  }
85
86  if (mOneShot) {
87    if (mEventCount > 1) {
88      sendFatalFailureToHost("One shot timer called multiple times ",
89                             &mStage);
90    } else {
91      test->markSuccess(mStage);
92    }
93  } else if (mEventCount == 3) {
94    // We mark recurring timers as successful on their third firing, if we
95    // can cancel it.
96    if (chreTimerCancel(mTimerHandle)) {
97      test->markSuccess(mStage);
98    } else {
99      sendFatalFailureToHost("Could not cancel recurring timer", &mStage);
100    }
101  }
102}
103
104void TimerSetTest::initStages() {
105  // To avoid fragmentation, we do one large allocation, and use
106  // placement new to initialize it.
107  mStages = static_cast<Stage*>(chreHeapAlloc(sizeof(*mStages) *
108                                              kStageCount));
109  if (mStages == nullptr) {
110    sendFatalFailureToHost("Insufficient heap");
111  }
112
113#define COOKIE(num) reinterpret_cast<const void*>(num)
114
115  // Stage 0: Test NULL cookie
116  new(&mStages[0]) Stage(0, kShortDuration, nullptr, true);
117  // Stage 1: Test (void*)-1 cookie
118  new(&mStages[1]) Stage(1, kShortDuration, COOKIE(-1), true);
119  // Stage 2: Test one shot with short duration
120  new(&mStages[2]) Stage(2, kShortDuration, COOKIE(2), true);
121  // Stage 3: Test one shot with long duration
122  new(&mStages[3]) Stage(3, kLongDuration,  COOKIE(3), true);
123  // Stage 4: Test recurring with long duration
124  new(&mStages[4]) Stage(4, kLongDuration,  COOKIE(4), false);
125  // Stage 5: Test recurring with short duration
126  new(&mStages[5]) Stage(5, kShortDuration, COOKIE(5), false);
127  static_assert((5 + 1) == kStageCount, "Missized array");
128
129#undef COOKIE
130}
131
132TimerSetTest::TimerSetTest()
133  : Test(CHRE_API_VERSION_1_0), mInMethod(false), mFinishedBitmask(0) {
134}
135
136void TimerSetTest::setUp(uint32_t messageSize, const void * /* message */) {
137  mInMethod = true;
138
139  if (messageSize != 0) {
140    sendFatalFailureToHost(
141        "TimerSet message expects 0 additional bytes, got ",
142        &messageSize);
143  }
144
145  initStages();
146  for (size_t i = 0; i < kStageCount; i++) {
147    mStages[i].start();
148  }
149
150  mInMethod = false;
151}
152
153TimerSetTest::~TimerSetTest() {
154  chreHeapFree(mStages);
155}
156
157void TimerSetTest::handleEvent(uint32_t senderInstanceId,
158                               uint16_t eventType, const void* eventData) {
159  uint64_t timestamp = chreGetTime();
160  if (mInMethod) {
161    sendFatalFailureToHost("handleEvent invoked while another nanoapp "
162                           "method is running");
163  }
164  mInMethod = true;
165  if (senderInstanceId != CHRE_INSTANCE_ID) {
166    sendFatalFailureToHost("handleEvent got event from unexpected sender:",
167                           &senderInstanceId);
168  }
169  if (eventType != CHRE_EVENT_TIMER) {
170    unexpectedEvent(eventType);
171  }
172  Stage *stage = getStageFromCookie(eventData);
173  if (stage == nullptr) {
174    sendFatalFailureToHost("handleEvent got invalid eventData");
175  }
176  stage->processEvent(timestamp, this);
177
178  mInMethod = false;
179}
180
181void TimerSetTest::markSuccess(uint32_t stage) {
182  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
183  uint32_t finishedBit = (1 << stage);
184  if ((kAllFinished & finishedBit) == 0) {
185    sendFatalFailureToHost("markSuccess bad stage", &stage);
186  }
187  mFinishedBitmask |= finishedBit;
188  if (mFinishedBitmask == kAllFinished) {
189    sendSuccessToHost();
190  }
191}
192
193TimerSetTest::Stage *TimerSetTest::getStageFromCookie(const void *cookie) {
194  Stage *ret = nullptr;
195  for (size_t i = 0; i < kStageCount; i++) {
196    if (mStages[i].getCookie() == cookie) {
197      if (ret != nullptr) {
198        sendInternalFailureToHost("Multiple stages with the same "
199                                  "cookie");
200      }
201      ret = &mStages[i];
202      // It's cheap enough to go through the whole array, and will
203      // catch if we screw up this test setup by duplicating a cookie.
204    }
205  }
206  return ret;
207}
208
209}  // namespace general_test
210