| /* |
| * Copyright (C) 2017 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. |
| */ |
| |
| #ifndef __VTS_HAL_HIDL_TARGET_CALLBACK_BASE_H |
| #define __VTS_HAL_HIDL_TARGET_CALLBACK_BASE_H |
| |
| #include <chrono> |
| #include <condition_variable> |
| #include <iostream> |
| #include <mutex> |
| #include <queue> |
| #include <unordered_map> |
| #include <utility> |
| |
| using namespace ::std; |
| using namespace ::std::chrono; |
| |
| constexpr char kVtsHalHidlTargetCallbackDefaultName[] = |
| "VtsHalHidlTargetCallbackDefaultName"; |
| constexpr milliseconds DEFAULT_CALLBACK_WAIT_TIMEOUT_INITIAL = minutes(1); |
| |
| namespace testing { |
| |
| /* |
| * VTS target side test template for callback. |
| * |
| * Providing wait and notify for callback functionality. |
| * |
| * A typical usage looks like this: |
| * |
| * class CallbackArgs { |
| * ArgType1 arg1; |
| * ArgType2 arg2; |
| * } |
| * |
| * class MyCallback |
| * : public ::testing::VtsHalHidlTargetCallbackBase<>, |
| * public CallbackInterface { |
| * public: |
| * CallbackApi1(ArgType1 arg1) { |
| * CallbackArgs data; |
| * data.arg1 = arg1; |
| * NotifyFromCallback("CallbackApi1", data); |
| * } |
| * |
| * CallbackApi2(ArgType2 arg2) { |
| * CallbackArgs data; |
| * data.arg1 = arg1; |
| * NotifyFromCallback("CallbackApi2", data); |
| * } |
| * } |
| * |
| * Test(MyTest) { |
| * CallApi1(); |
| * CallApi2(); |
| * auto result = cb_.WaitForCallback("CallbackApi1"); |
| * // cb_ as an instance of MyCallback, result is an instance of |
| * // ::testing::VtsHalHidlTargetCallbackBase::WaitForCallbackResult |
| * EXPECT_TRUE(result.no_timeout); // Check wait did not time out |
| * EXPECT_TRUE(result.args); // Check CallbackArgs is received (not |
| * nullptr). This is optional. |
| * // Here check value of args using the pointer result.args; |
| * result = cb_.WaitForCallback("CallbackApi2"); |
| * EXPECT_TRUE(result.no_timeout); |
| * // Here check value of args using the pointer result.args; |
| * |
| * // Additionally. a test can wait for one of multiple callbacks. |
| * // In this case, wait will return when any of the callbacks in the provided |
| * // name list is called. |
| * result = cb_.WaitForCallbackAny(<vector_of_string>) |
| * // When vector_of_string is not provided, all callback functions will |
| * // be monitored. The name of callback function that was invoked |
| * // is stored in result.name |
| * } |
| * |
| * Note type of CallbackArgsTemplateClass is same across the class, which means |
| * all WaitForCallback method will return the same data type. |
| */ |
| template <class CallbackArgsTemplateClass> |
| class VtsHalHidlTargetCallbackBase { |
| public: |
| struct WaitForCallbackResult { |
| WaitForCallbackResult() |
| : no_timeout(false), |
| args(shared_ptr<CallbackArgsTemplateClass>(nullptr)), |
| name("") {} |
| |
| // Whether the wait timed out |
| bool no_timeout; |
| // Arguments data from callback functions. Defaults to nullptr. |
| shared_ptr<CallbackArgsTemplateClass> args; |
| // Name of the callback. Defaults to empty string. |
| string name; |
| }; |
| |
| VtsHalHidlTargetCallbackBase() |
| : cb_default_wait_timeout_(DEFAULT_CALLBACK_WAIT_TIMEOUT_INITIAL) {} |
| |
| virtual ~VtsHalHidlTargetCallbackBase() { |
| for (auto it : cb_lock_map_) { |
| delete it.second; |
| } |
| } |
| |
| /* |
| * Wait for a callback function in a test. |
| * Returns a WaitForCallbackResult object containing wait results. |
| * If callback_function_name is not provided, a default name will be used. |
| * Timeout defaults to -1 milliseconds. Negative timeout means use to |
| * use the time out set for the callback or default callback wait time out. |
| */ |
| WaitForCallbackResult WaitForCallback( |
| const string& callback_function_name = |
| kVtsHalHidlTargetCallbackDefaultName, |
| milliseconds timeout = milliseconds(-1)) { |
| return GetCallbackLock(callback_function_name)->WaitForCallback(timeout); |
| } |
| |
| /* |
| * Wait for any of the callback functions specified. |
| * Returns a WaitForCallbackResult object containing wait results. |
| * If callback_function_names is not provided, all callback functions will |
| * be monitored, and the list of callback functions will be updated |
| * dynamically during run time. |
| * If timeout_any is not provided, the shortest timeout from the function |
| * list will be used. |
| */ |
| WaitForCallbackResult WaitForCallbackAny( |
| const vector<string>& callback_function_names = vector<string>(), |
| milliseconds timeout_any = milliseconds(-1)) { |
| unique_lock<mutex> lock(cb_wait_any_mtx_); |
| |
| auto start_time = steady_clock::now(); |
| |
| WaitForCallbackResult res = PeekCallbackLocks(callback_function_names); |
| while (!res.no_timeout) { |
| auto expiration = |
| GetWaitAnyTimeout(callback_function_names, start_time, timeout_any); |
| auto status = cb_wait_any_cv_.wait_until(lock, expiration); |
| if (status == cv_status::timeout) { |
| cerr << "Timed out waiting for callback functions." << endl; |
| break; |
| } |
| res = PeekCallbackLocks(callback_function_names); |
| } |
| return res; |
| } |
| |
| /* |
| * Notify a waiting test when a callback is invoked. |
| * If callback_function_name is not provided, a default name will be used. |
| */ |
| void NotifyFromCallback(const string& callback_function_name = |
| kVtsHalHidlTargetCallbackDefaultName) { |
| unique_lock<mutex> lock(cb_wait_any_mtx_); |
| GetCallbackLock(callback_function_name)->NotifyFromCallback(); |
| cb_wait_any_cv_.notify_one(); |
| } |
| |
| /* |
| * Notify a waiting test with data when a callback is invoked. |
| */ |
| void NotifyFromCallback(const CallbackArgsTemplateClass& data) { |
| NotifyFromCallback(kVtsHalHidlTargetCallbackDefaultName, data); |
| } |
| |
| /* |
| * Notify a waiting test with data when a callback is invoked. |
| * If callback_function_name is not provided, a default name will be used. |
| */ |
| void NotifyFromCallback(const string& callback_function_name, |
| const CallbackArgsTemplateClass& data) { |
| unique_lock<mutex> lock(cb_wait_any_mtx_); |
| GetCallbackLock(callback_function_name)->NotifyFromCallback(data); |
| cb_wait_any_cv_.notify_one(); |
| } |
| |
| /* |
| * Clear lock and data for a callback function. |
| * This function is optional. |
| */ |
| void ClearForCallback(const string& callback_function_name = |
| kVtsHalHidlTargetCallbackDefaultName) { |
| GetCallbackLock(callback_function_name, true); |
| } |
| |
| /* |
| * Get wait timeout for a specific callback function. |
| * If callback_function_name is not provided, a default name will be used. |
| */ |
| milliseconds GetWaitTimeout(const string& callback_function_name = |
| kVtsHalHidlTargetCallbackDefaultName) { |
| return GetCallbackLock(callback_function_name)->GetWaitTimeout(); |
| } |
| |
| /* |
| * Set wait timeout for a specific callback function. |
| * To set a default timeout (not for the default function name), |
| * use SetWaitTimeoutDefault. default function name callback timeout will |
| * also be set by SetWaitTimeoutDefault. |
| */ |
| void SetWaitTimeout(const string& callback_function_name, |
| milliseconds timeout) { |
| GetCallbackLock(callback_function_name)->SetWaitTimeout(timeout); |
| } |
| |
| /* |
| * Get default wait timeout for a callback function. |
| * The default timeout is valid for all callback function names that |
| * have not been specified a timeout value, including default function name. |
| */ |
| milliseconds GetWaitTimeoutDefault() { return cb_default_wait_timeout_; } |
| |
| /* |
| * Set default wait timeout for a callback function. |
| * The default timeout is valid for all callback function names that |
| * have not been specified a timeout value, including default function name. |
| */ |
| void SetWaitTimeoutDefault(milliseconds timeout) { |
| cb_default_wait_timeout_ = timeout; |
| } |
| |
| private: |
| /* |
| * A utility class to store semaphore and data for a callback name. |
| */ |
| class CallbackLock { |
| public: |
| CallbackLock(VtsHalHidlTargetCallbackBase& parent, const string& name) |
| : wait_count_(0), |
| parent_(parent), |
| timeout_(milliseconds(-1)), |
| name_(name) {} |
| |
| /* |
| * Wait for represented callback function. |
| * Timeout defaults to -1 milliseconds. Negative timeout means use to |
| * use the time out set for the callback or default callback wait time out. |
| */ |
| WaitForCallbackResult WaitForCallback( |
| milliseconds timeout = milliseconds(-1), |
| bool no_wait_blocking = false) { |
| return Wait(timeout, no_wait_blocking); |
| } |
| |
| /* |
| * Wait for represented callback function. |
| * Timeout defaults to -1 milliseconds. Negative timeout means use to |
| * use the time out set for the callback or default callback wait time out. |
| */ |
| WaitForCallbackResult WaitForCallback(bool no_wait_blocking) { |
| return Wait(milliseconds(-1), no_wait_blocking); |
| } |
| |
| /* Notify from represented callback function. */ |
| void NotifyFromCallback() { |
| unique_lock<mutex> lock(wait_mtx_); |
| Notify(); |
| } |
| |
| /* Notify from represented callback function with data. */ |
| void NotifyFromCallback(const CallbackArgsTemplateClass& data) { |
| unique_lock<mutex> wait_lock(wait_mtx_); |
| arg_data_.push(make_shared<CallbackArgsTemplateClass>(data)); |
| Notify(); |
| } |
| |
| /* Set wait timeout for represented callback function. */ |
| void SetWaitTimeout(milliseconds timeout) { timeout_ = timeout; } |
| |
| /* Get wait timeout for represented callback function. */ |
| milliseconds GetWaitTimeout() { |
| if (timeout_ < milliseconds(0)) { |
| return parent_.GetWaitTimeoutDefault(); |
| } |
| return timeout_; |
| } |
| |
| private: |
| /* |
| * Wait for represented callback function in a test. |
| * Returns a WaitForCallbackResult object containing wait results. |
| * Timeout defaults to -1 milliseconds. Negative timeout means use to |
| * use the time out set for the callback or default callback wait time out. |
| */ |
| WaitForCallbackResult Wait(milliseconds timeout, bool no_wait_blocking) { |
| unique_lock<mutex> lock(wait_mtx_); |
| WaitForCallbackResult res; |
| res.name = name_; |
| if (!no_wait_blocking) { |
| if (timeout < milliseconds(0)) { |
| timeout = GetWaitTimeout(); |
| } |
| auto expiration = steady_clock::now() + timeout; |
| while (wait_count_ == 0) { |
| auto status = wait_cv_.wait_until(lock, expiration); |
| if (status == cv_status::timeout) { |
| cerr << "Timed out waiting for callback" << endl; |
| return res; |
| } |
| } |
| } else if (!wait_count_) { |
| return res; |
| } |
| |
| wait_count_--; |
| res.no_timeout = true; |
| if (!arg_data_.empty()) { |
| res.args = arg_data_.front(); |
| arg_data_.pop(); |
| } |
| return res; |
| } |
| |
| /* Notify from represented callback function. */ |
| void Notify() { |
| wait_count_++; |
| wait_cv_.notify_one(); |
| } |
| |
| // Mutex for protecting operations on wait count and conditional variable |
| mutex wait_mtx_; |
| // Conditional variable for callback wait and notify |
| condition_variable wait_cv_; |
| // Count for callback conditional variable |
| unsigned int wait_count_; |
| // A queue of callback arg data |
| queue<shared_ptr<CallbackArgsTemplateClass>> arg_data_; |
| // Pointer to parent class |
| VtsHalHidlTargetCallbackBase& parent_; |
| // Wait time out |
| milliseconds timeout_; |
| // Name of the represented callback function |
| string name_; |
| }; |
| |
| /* |
| * Get CallbackLock object using callback function name. |
| * If callback_function_name is not provided, a default name will be used. |
| * If callback_function_name does not exists in map yet, a new CallbackLock |
| * object will be created. |
| * If auto_clear is true, the old CallbackLock will be deleted. |
| */ |
| CallbackLock* GetCallbackLock(const string& callback_function_name, |
| bool auto_clear = false) { |
| unique_lock<mutex> lock(cb_lock_map_mtx_); |
| auto found = cb_lock_map_.find(callback_function_name); |
| if (found == cb_lock_map_.end()) { |
| CallbackLock* result = new CallbackLock(*this, callback_function_name); |
| cb_lock_map_.insert({callback_function_name, result}); |
| return result; |
| } else { |
| if (auto_clear) { |
| delete (found->second); |
| found->second = new CallbackLock(*this, callback_function_name); |
| } |
| return found->second; |
| } |
| } |
| |
| /* |
| * Get wait timeout for a list of function names. |
| * If timeout_any is not negative, start_time + timeout_any will be returned. |
| * Otherwise, the shortest timeout from the list will be returned. |
| */ |
| steady_clock::time_point GetWaitAnyTimeout( |
| const vector<string>& callback_function_names, |
| steady_clock::time_point start_time, milliseconds timeout_any) { |
| if (timeout_any >= milliseconds(0)) { |
| return start_time + timeout_any; |
| } |
| |
| auto locks = GetWaitAnyCallbackLocks(callback_function_names); |
| |
| auto timeout_min = steady_clock::duration::max(); |
| for (auto lock : locks) { |
| auto timeout = lock->GetWaitTimeout(); |
| if (timeout < timeout_min) { |
| timeout_min = timeout; |
| } |
| } |
| |
| return start_time + timeout_min; |
| } |
| |
| /* |
| * Get a list of CallbackLock pointers from provided function name list. |
| */ |
| vector<CallbackLock*> GetWaitAnyCallbackLocks( |
| const vector<string>& callback_function_names) { |
| vector<CallbackLock*> res; |
| if (callback_function_names.empty()) { |
| for (auto const& it : cb_lock_map_) { |
| res.push_back(it.second); |
| } |
| } else { |
| for (auto const& name : callback_function_names) { |
| res.push_back(GetCallbackLock(name)); |
| } |
| } |
| return res; |
| } |
| |
| /* |
| * Peek into the list of callback locks to check whether any of the |
| * callback functions has been called. |
| */ |
| WaitForCallbackResult PeekCallbackLocks( |
| const vector<string>& callback_function_names) { |
| auto locks = GetWaitAnyCallbackLocks(callback_function_names); |
| for (auto lock : locks) { |
| auto test = lock->WaitForCallback(true); |
| if (test.no_timeout) { |
| return test; |
| } |
| } |
| WaitForCallbackResult res; |
| return res; |
| } |
| |
| // A map of function name and CallbackLock object pointers |
| unordered_map<string, CallbackLock*> cb_lock_map_; |
| // Mutex for protecting operations on lock map |
| mutex cb_lock_map_mtx_; |
| // Mutex for protecting waiting any callback |
| mutex cb_wait_any_mtx_; |
| // Default wait timeout |
| milliseconds cb_default_wait_timeout_; |
| // Conditional variable for any callback notify |
| condition_variable cb_wait_any_cv_; |
| }; |
| |
| } // namespace testing |
| |
| #endif // __VTS_HAL_HIDL_TARGET_CALLBACK_BASE_H |