2021-08-12 21:47:03 +02:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2021 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 <aidl/Gtest.h>
|
|
|
|
#include <aidl/Vintf.h>
|
|
|
|
|
|
|
|
#include "VtsHalContexthubUtilsCommon.h"
|
|
|
|
|
|
|
|
#include <android/hardware/contexthub/BnContextHub.h>
|
|
|
|
#include <android/hardware/contexthub/BnContextHubCallback.h>
|
|
|
|
#include <android/hardware/contexthub/IContextHub.h>
|
|
|
|
#include <android/hardware/contexthub/IContextHubCallback.h>
|
|
|
|
#include <binder/IServiceManager.h>
|
|
|
|
#include <binder/ProcessState.h>
|
|
|
|
#include <log/log.h>
|
|
|
|
|
|
|
|
#include <cinttypes>
|
|
|
|
#include <future>
|
|
|
|
|
|
|
|
using ::android::ProcessState;
|
|
|
|
using ::android::sp;
|
|
|
|
using ::android::String16;
|
|
|
|
using ::android::binder::Status;
|
|
|
|
using ::android::hardware::contexthub::AsyncEventType;
|
|
|
|
using ::android::hardware::contexthub::ContextHubInfo;
|
|
|
|
using ::android::hardware::contexthub::ContextHubMessage;
|
2021-11-19 01:24:45 +01:00
|
|
|
using ::android::hardware::contexthub::HostEndpointInfo;
|
2021-08-12 21:47:03 +02:00
|
|
|
using ::android::hardware::contexthub::IContextHub;
|
|
|
|
using ::android::hardware::contexthub::IContextHubCallbackDefault;
|
|
|
|
using ::android::hardware::contexthub::NanoappBinary;
|
|
|
|
using ::android::hardware::contexthub::NanoappInfo;
|
2021-12-09 19:30:49 +01:00
|
|
|
using ::android::hardware::contexthub::NanoappRpcService;
|
2021-08-12 21:47:03 +02:00
|
|
|
using ::android::hardware::contexthub::Setting;
|
|
|
|
using ::android::hardware::contexthub::vts_utils::kNonExistentAppId;
|
|
|
|
using ::android::hardware::contexthub::vts_utils::waitForCallback;
|
|
|
|
|
|
|
|
class ContextHubAidl : public testing::TestWithParam<std::tuple<std::string, int32_t>> {
|
|
|
|
public:
|
|
|
|
virtual void SetUp() override {
|
|
|
|
contextHub = android::waitForDeclaredService<IContextHub>(
|
|
|
|
String16(std::get<0>(GetParam()).c_str()));
|
|
|
|
ASSERT_NE(contextHub, nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t getHubId() { return std::get<1>(GetParam()); }
|
|
|
|
|
|
|
|
void testSettingChanged(Setting setting);
|
|
|
|
|
|
|
|
sp<IContextHub> contextHub;
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_P(ContextHubAidl, TestGetHubs) {
|
|
|
|
std::vector<ContextHubInfo> hubs;
|
|
|
|
ASSERT_TRUE(contextHub->getContextHubs(&hubs).isOk());
|
|
|
|
|
|
|
|
ALOGD("System reports %zu hubs", hubs.size());
|
|
|
|
|
|
|
|
for (const ContextHubInfo& hub : hubs) {
|
|
|
|
ALOGD("Checking hub ID %" PRIu32, hub.id);
|
|
|
|
|
|
|
|
EXPECT_GT(hub.name.size(), 0);
|
|
|
|
EXPECT_GT(hub.vendor.size(), 0);
|
|
|
|
EXPECT_GT(hub.toolchain.size(), 0);
|
|
|
|
EXPECT_GT(hub.peakMips, 0);
|
|
|
|
EXPECT_GT(hub.chrePlatformId, 0);
|
|
|
|
EXPECT_GT(hub.chreApiMajorVersion, 0);
|
2021-10-28 23:59:55 +02:00
|
|
|
EXPECT_GE(hub.chreApiMinorVersion, 0);
|
|
|
|
EXPECT_GE(hub.chrePatchVersion, 0);
|
2021-08-12 21:47:03 +02:00
|
|
|
|
|
|
|
// Minimum 128 byte MTU as required by CHRE API v1.0
|
|
|
|
EXPECT_GE(hub.maxSupportedMessageLengthBytes, UINT32_C(128));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-29 18:01:35 +02:00
|
|
|
class EmptyContextHubCallback : public android::hardware::contexthub::BnContextHubCallback {
|
|
|
|
public:
|
|
|
|
Status handleNanoappInfo(const std::vector<NanoappInfo>& /* appInfo */) override {
|
|
|
|
return Status::ok();
|
|
|
|
}
|
|
|
|
|
|
|
|
Status handleContextHubMessage(const ContextHubMessage& /* msg */,
|
|
|
|
const std::vector<String16>& /* msgContentPerms */) override {
|
|
|
|
return Status::ok();
|
|
|
|
}
|
|
|
|
|
|
|
|
Status handleContextHubAsyncEvent(AsyncEventType /* evt */) override { return Status::ok(); }
|
|
|
|
|
|
|
|
Status handleTransactionResult(int32_t /* transactionId */, bool /* success */) override {
|
|
|
|
return Status::ok();
|
|
|
|
}
|
2022-12-22 15:21:31 +01:00
|
|
|
|
|
|
|
Status handleNanSessionRequest(bool /* enable */) override { return Status::ok(); }
|
2021-10-29 18:01:35 +02:00
|
|
|
};
|
|
|
|
|
2021-08-12 21:47:03 +02:00
|
|
|
TEST_P(ContextHubAidl, TestRegisterCallback) {
|
2021-10-29 18:01:35 +02:00
|
|
|
sp<EmptyContextHubCallback> cb = sp<EmptyContextHubCallback>::make();
|
2022-01-06 23:42:10 +01:00
|
|
|
ASSERT_TRUE(contextHub->registerCallback(getHubId(), cb).isOk());
|
2021-08-12 21:47:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(ContextHubAidl, TestRegisterNullCallback) {
|
2022-01-06 23:42:10 +01:00
|
|
|
ASSERT_TRUE(contextHub->registerCallback(getHubId(), nullptr).isOk());
|
2021-08-12 21:47:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Helper callback that puts the async appInfo callback data into a promise
|
|
|
|
class QueryAppsCallback : public android::hardware::contexthub::BnContextHubCallback {
|
|
|
|
public:
|
|
|
|
Status handleNanoappInfo(const std::vector<NanoappInfo>& appInfo) override {
|
|
|
|
ALOGD("Got app info callback with %zu apps", appInfo.size());
|
|
|
|
promise.set_value(appInfo);
|
|
|
|
return Status::ok();
|
|
|
|
}
|
|
|
|
|
|
|
|
Status handleContextHubMessage(const ContextHubMessage& /* msg */,
|
|
|
|
const std::vector<String16>& /* msgContentPerms */) override {
|
|
|
|
return Status::ok();
|
|
|
|
}
|
|
|
|
|
|
|
|
Status handleContextHubAsyncEvent(AsyncEventType /* evt */) override { return Status::ok(); }
|
|
|
|
|
|
|
|
Status handleTransactionResult(int32_t /* transactionId */, bool /* success */) override {
|
|
|
|
return Status::ok();
|
|
|
|
}
|
|
|
|
|
2022-12-22 15:21:31 +01:00
|
|
|
Status handleNanSessionRequest(bool /* enable */) override { return Status::ok(); }
|
|
|
|
|
2021-08-12 21:47:03 +02:00
|
|
|
std::promise<std::vector<NanoappInfo>> promise;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Calls queryApps() and checks the returned metadata
|
|
|
|
TEST_P(ContextHubAidl, TestQueryApps) {
|
|
|
|
sp<QueryAppsCallback> cb = sp<QueryAppsCallback>::make();
|
2022-01-06 23:42:10 +01:00
|
|
|
ASSERT_TRUE(contextHub->registerCallback(getHubId(), cb).isOk());
|
|
|
|
ASSERT_TRUE(contextHub->queryNanoapps(getHubId()).isOk());
|
2021-08-12 21:47:03 +02:00
|
|
|
|
|
|
|
std::vector<NanoappInfo> appInfoList;
|
|
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future(), &appInfoList));
|
|
|
|
for (const NanoappInfo& appInfo : appInfoList) {
|
|
|
|
EXPECT_NE(appInfo.nanoappId, UINT64_C(0));
|
|
|
|
EXPECT_NE(appInfo.nanoappId, kNonExistentAppId);
|
2021-12-09 19:30:49 +01:00
|
|
|
|
|
|
|
// Verify services are unique.
|
|
|
|
std::set<uint64_t> existingServiceIds;
|
|
|
|
for (const NanoappRpcService& rpcService : appInfo.rpcServices) {
|
|
|
|
EXPECT_NE(rpcService.id, UINT64_C(0));
|
|
|
|
EXPECT_EQ(existingServiceIds.count(rpcService.id), 0);
|
|
|
|
existingServiceIds.insert(rpcService.id);
|
|
|
|
}
|
2021-08-12 21:47:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-08 17:49:47 +01:00
|
|
|
// Calls getPreloadedNanoapps() and verifies there are preloaded nanoapps
|
|
|
|
TEST_P(ContextHubAidl, TestGetPreloadedNanoapps) {
|
|
|
|
std::vector<int64_t> preloadedNanoappIds;
|
|
|
|
Status status = contextHub->getPreloadedNanoappIds(&preloadedNanoappIds);
|
|
|
|
if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
|
|
|
|
status.transactionError() == android::UNKNOWN_TRANSACTION) {
|
|
|
|
return; // not supported -> old API; or not implemented
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_TRUE(status.isOk());
|
|
|
|
ASSERT_FALSE(preloadedNanoappIds.empty());
|
|
|
|
}
|
|
|
|
|
2021-08-12 21:47:03 +02:00
|
|
|
// Helper callback that puts the TransactionResult for the expectedTransactionId into a
|
|
|
|
// promise
|
|
|
|
class TransactionResultCallback : public android::hardware::contexthub::BnContextHubCallback {
|
|
|
|
public:
|
|
|
|
Status handleNanoappInfo(const std::vector<NanoappInfo>& /* appInfo */) override {
|
|
|
|
return Status::ok();
|
|
|
|
}
|
|
|
|
|
|
|
|
Status handleContextHubMessage(const ContextHubMessage& /* msg */,
|
|
|
|
const std::vector<String16>& /* msgContentPerms */) override {
|
|
|
|
return Status::ok();
|
|
|
|
}
|
|
|
|
|
|
|
|
Status handleContextHubAsyncEvent(AsyncEventType /* evt */) override { return Status::ok(); }
|
|
|
|
|
|
|
|
Status handleTransactionResult(int32_t transactionId, bool success) override {
|
|
|
|
ALOGD("Got transaction result callback for transactionId %" PRIu32 " (expecting %" PRIu32
|
|
|
|
") with success %d",
|
|
|
|
transactionId, expectedTransactionId, success);
|
|
|
|
if (transactionId == expectedTransactionId) {
|
|
|
|
promise.set_value(success);
|
|
|
|
}
|
|
|
|
return Status::ok();
|
|
|
|
}
|
|
|
|
|
2022-12-22 15:21:31 +01:00
|
|
|
Status handleNanSessionRequest(bool /* enable */) override { return Status::ok(); }
|
|
|
|
|
2021-08-12 21:47:03 +02:00
|
|
|
uint32_t expectedTransactionId = 0;
|
|
|
|
std::promise<bool> promise;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Parameterized fixture that sets the callback to TransactionResultCallback
|
|
|
|
class ContextHubTransactionTest : public ContextHubAidl {
|
|
|
|
public:
|
|
|
|
virtual void SetUp() override {
|
|
|
|
ContextHubAidl::SetUp();
|
2022-01-06 23:42:10 +01:00
|
|
|
ASSERT_TRUE(contextHub->registerCallback(getHubId(), cb).isOk());
|
2021-08-12 21:47:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sp<TransactionResultCallback> cb = sp<TransactionResultCallback>::make();
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_P(ContextHubTransactionTest, TestSendMessageToNonExistentNanoapp) {
|
|
|
|
ContextHubMessage message;
|
|
|
|
message.nanoappId = kNonExistentAppId;
|
|
|
|
message.messageType = 1;
|
|
|
|
message.messageBody.resize(4);
|
|
|
|
std::fill(message.messageBody.begin(), message.messageBody.end(), 0);
|
|
|
|
|
|
|
|
ALOGD("Sending message to non-existent nanoapp");
|
2022-01-06 23:42:10 +01:00
|
|
|
ASSERT_TRUE(contextHub->sendMessageToHub(getHubId(), message).isOk());
|
2021-08-12 21:47:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(ContextHubTransactionTest, TestLoadEmptyNanoapp) {
|
|
|
|
cb->expectedTransactionId = 0123;
|
|
|
|
NanoappBinary emptyApp;
|
|
|
|
|
|
|
|
emptyApp.nanoappId = kNonExistentAppId;
|
|
|
|
emptyApp.nanoappVersion = 1;
|
|
|
|
emptyApp.flags = 0;
|
|
|
|
emptyApp.targetChreApiMajorVersion = 1;
|
|
|
|
emptyApp.targetChreApiMinorVersion = 0;
|
|
|
|
|
|
|
|
ALOGD("Loading empty nanoapp");
|
2022-01-06 23:42:10 +01:00
|
|
|
bool success = contextHub->loadNanoapp(getHubId(), emptyApp, cb->expectedTransactionId).isOk();
|
2021-08-12 21:47:03 +02:00
|
|
|
if (success) {
|
|
|
|
bool transactionSuccess;
|
|
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future(), &transactionSuccess));
|
|
|
|
ASSERT_FALSE(transactionSuccess);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(ContextHubTransactionTest, TestUnloadNonexistentNanoapp) {
|
|
|
|
cb->expectedTransactionId = 1234;
|
|
|
|
|
|
|
|
ALOGD("Unloading nonexistent nanoapp");
|
2022-01-06 23:42:10 +01:00
|
|
|
bool success =
|
|
|
|
contextHub->unloadNanoapp(getHubId(), kNonExistentAppId, cb->expectedTransactionId)
|
|
|
|
.isOk();
|
2021-08-12 21:47:03 +02:00
|
|
|
if (success) {
|
|
|
|
bool transactionSuccess;
|
|
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future(), &transactionSuccess));
|
|
|
|
ASSERT_FALSE(transactionSuccess);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(ContextHubTransactionTest, TestEnableNonexistentNanoapp) {
|
|
|
|
cb->expectedTransactionId = 2345;
|
|
|
|
|
|
|
|
ALOGD("Enabling nonexistent nanoapp");
|
2022-01-06 23:42:10 +01:00
|
|
|
bool success =
|
|
|
|
contextHub->enableNanoapp(getHubId(), kNonExistentAppId, cb->expectedTransactionId)
|
|
|
|
.isOk();
|
2021-08-12 21:47:03 +02:00
|
|
|
if (success) {
|
|
|
|
bool transactionSuccess;
|
|
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future(), &transactionSuccess));
|
|
|
|
ASSERT_FALSE(transactionSuccess);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(ContextHubTransactionTest, TestDisableNonexistentNanoapp) {
|
|
|
|
cb->expectedTransactionId = 3456;
|
|
|
|
|
|
|
|
ALOGD("Disabling nonexistent nanoapp");
|
2022-01-06 23:42:10 +01:00
|
|
|
bool success =
|
|
|
|
contextHub->disableNanoapp(getHubId(), kNonExistentAppId, cb->expectedTransactionId)
|
|
|
|
.isOk();
|
2021-08-12 21:47:03 +02:00
|
|
|
if (success) {
|
|
|
|
bool transactionSuccess;
|
|
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future(), &transactionSuccess));
|
|
|
|
ASSERT_FALSE(transactionSuccess);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ContextHubAidl::testSettingChanged(Setting setting) {
|
|
|
|
// In VTS, we only test that sending the values doesn't cause things to blow up - GTS tests
|
|
|
|
// verify the expected E2E behavior in CHRE
|
2021-10-29 18:01:35 +02:00
|
|
|
sp<EmptyContextHubCallback> cb = sp<EmptyContextHubCallback>::make();
|
2022-01-06 23:42:10 +01:00
|
|
|
ASSERT_TRUE(contextHub->registerCallback(getHubId(), cb).isOk());
|
2021-08-12 21:47:03 +02:00
|
|
|
|
|
|
|
ASSERT_TRUE(contextHub->onSettingChanged(setting, true /* enabled */).isOk());
|
|
|
|
ASSERT_TRUE(contextHub->onSettingChanged(setting, false /* enabled */).isOk());
|
|
|
|
|
2022-01-06 23:42:10 +01:00
|
|
|
ASSERT_TRUE(contextHub->registerCallback(getHubId(), nullptr).isOk());
|
2021-08-12 21:47:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(ContextHubAidl, TestOnLocationSettingChanged) {
|
|
|
|
testSettingChanged(Setting::LOCATION);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(ContextHubAidl, TestOnWifiMainSettingChanged) {
|
|
|
|
testSettingChanged(Setting::WIFI_MAIN);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(ContextHubAidl, TestOnWifiScanningSettingChanged) {
|
|
|
|
testSettingChanged(Setting::WIFI_SCANNING);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(ContextHubAidl, TestOnAirplaneModeSettingChanged) {
|
|
|
|
testSettingChanged(Setting::AIRPLANE_MODE);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(ContextHubAidl, TestOnMicrophoneSettingChanged) {
|
|
|
|
testSettingChanged(Setting::MICROPHONE);
|
|
|
|
}
|
|
|
|
|
2022-01-20 21:49:10 +01:00
|
|
|
TEST_P(ContextHubAidl, TestOnBtMainSettingChanged) {
|
|
|
|
testSettingChanged(Setting::BT_MAIN);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(ContextHubAidl, TestOnBtScanningSettingChanged) {
|
|
|
|
testSettingChanged(Setting::BT_SCANNING);
|
|
|
|
}
|
|
|
|
|
2021-08-12 21:47:03 +02:00
|
|
|
std::vector<std::tuple<std::string, int32_t>> generateContextHubMapping() {
|
|
|
|
std::vector<std::tuple<std::string, int32_t>> tuples;
|
|
|
|
auto contextHubAidlNames = android::getAidlHalInstanceNames(IContextHub::descriptor);
|
|
|
|
std::vector<ContextHubInfo> contextHubInfos;
|
|
|
|
|
|
|
|
for (int i = 0; i < contextHubAidlNames.size(); i++) {
|
|
|
|
auto contextHubName = contextHubAidlNames[i].c_str();
|
|
|
|
auto contextHub = android::waitForDeclaredService<IContextHub>(String16(contextHubName));
|
|
|
|
if (contextHub->getContextHubs(&contextHubInfos).isOk()) {
|
|
|
|
for (auto& info : contextHubInfos) {
|
|
|
|
tuples.push_back(std::make_tuple(contextHubName, info.id));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return tuples;
|
|
|
|
}
|
|
|
|
|
2021-11-19 01:24:45 +01:00
|
|
|
TEST_P(ContextHubAidl, TestHostConnection) {
|
|
|
|
constexpr char16_t kHostEndpointId = 1;
|
|
|
|
HostEndpointInfo hostEndpointInfo;
|
|
|
|
hostEndpointInfo.hostEndpointId = kHostEndpointId;
|
|
|
|
|
|
|
|
ASSERT_TRUE(contextHub->onHostEndpointConnected(hostEndpointInfo).isOk());
|
|
|
|
ASSERT_TRUE(contextHub->onHostEndpointDisconnected(kHostEndpointId).isOk());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(ContextHubAidl, TestInvalidHostConnection) {
|
|
|
|
constexpr char16_t kHostEndpointId = 1;
|
|
|
|
|
2022-02-01 18:03:32 +01:00
|
|
|
ASSERT_TRUE(contextHub->onHostEndpointDisconnected(kHostEndpointId).isOk());
|
2021-11-19 01:24:45 +01:00
|
|
|
}
|
|
|
|
|
2022-12-22 15:21:31 +01:00
|
|
|
TEST_P(ContextHubAidl, TestNanSessionStateChange) {
|
|
|
|
ASSERT_TRUE(contextHub->onNanSessionStateChanged(true /*state*/).isOk());
|
|
|
|
ASSERT_TRUE(contextHub->onNanSessionStateChanged(false /*state*/).isOk());
|
|
|
|
}
|
|
|
|
|
2021-08-12 21:47:03 +02:00
|
|
|
std::string PrintGeneratedTest(const testing::TestParamInfo<ContextHubAidl::ParamType>& info) {
|
|
|
|
return std::string("CONTEXT_HUB_ID_") + std::to_string(std::get<1>(info.param));
|
|
|
|
}
|
|
|
|
|
|
|
|
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ContextHubAidl);
|
|
|
|
INSTANTIATE_TEST_SUITE_P(ContextHub, ContextHubAidl, testing::ValuesIn(generateContextHubMapping()),
|
|
|
|
PrintGeneratedTest);
|
|
|
|
|
|
|
|
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ContextHubTransactionTest);
|
|
|
|
INSTANTIATE_TEST_SUITE_P(ContextHub, ContextHubTransactionTest,
|
|
|
|
testing::ValuesIn(generateContextHubMapping()), PrintGeneratedTest);
|
|
|
|
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
|
|
ProcessState::self()->setThreadPoolMaxThreadCount(1);
|
|
|
|
ProcessState::self()->startThreadPool();
|
|
|
|
return RUN_ALL_TESTS();
|
|
|
|
}
|