/* * 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 #include #include #include #include #include #include "vhal_v2_0/VehicleHalManager.h" #include "VehicleHalTestUtils.h" namespace android { namespace hardware { namespace automotive { namespace vehicle { namespace V2_0 { // A simple helper class to expose 'cmdSetOneProperty' to the unit tests. class VehicleHalManagerTestHelper { public: VehicleHalManagerTestHelper(VehicleHalManager* manager) { mManager = manager; } bool cmdSetOneProperty(int fd, const hidl_vec& options) { return mManager->cmdSetOneProperty(fd, options); } public: VehicleHalManager* mManager; }; namespace { using namespace std::placeholders; constexpr char kCarMake[] = "Default Car"; constexpr int kRetriablePropMockedAttempts = 3; class MockedVehicleHal : public VehicleHal { public: MockedVehicleHal() { mConfigs.assign(std::begin(kVehicleProperties), std::end(kVehicleProperties)); } std::vector listProperties() override { return mConfigs; } VehiclePropValuePtr get(const VehiclePropValue& requestedPropValue, StatusCode* outStatus) override { *outStatus = StatusCode::OK; VehiclePropValuePtr pValue; auto property = static_cast(requestedPropValue.prop); int32_t areaId = requestedPropValue.areaId; if (property == VehicleProperty::INFO_FUEL_CAPACITY) { if (fuelCapacityAttemptsLeft-- > 0) { // Emulate property not ready yet. *outStatus = StatusCode::TRY_AGAIN; } else { pValue = getValuePool()->obtainFloat(42.42); } } else { auto key = makeKey(requestedPropValue); if (mValues.count(key) == 0) { ALOGW("key not found\n"); *outStatus = StatusCode::INVALID_ARG; return pValue; } pValue = getValuePool()->obtain(mValues[key]); } if (*outStatus == StatusCode::OK && pValue.get() != nullptr) { pValue->prop = toInt(property); pValue->areaId = areaId; pValue->timestamp = elapsedRealtimeNano(); } return pValue; } StatusCode set(const VehiclePropValue& propValue) override { if (toInt(VehicleProperty::MIRROR_FOLD) == propValue.prop && mirrorFoldAttemptsLeft-- > 0) { return StatusCode::TRY_AGAIN; } mValues[makeKey(propValue)] = propValue; return StatusCode::OK; } StatusCode subscribe(int32_t /* property */, float /* sampleRate */) override { return StatusCode::OK; } StatusCode unsubscribe(int32_t /* property */) override { return StatusCode::OK; } void sendPropEvent(recyclable_ptr value) { doHalEvent(std::move(value)); } void sendHalError(StatusCode error, int32_t property, int32_t areaId) { doHalPropertySetError(error, property, areaId); } public: int fuelCapacityAttemptsLeft = kRetriablePropMockedAttempts; int mirrorFoldAttemptsLeft = kRetriablePropMockedAttempts; private: int64_t makeKey(const VehiclePropValue& v) const { return makeKey(v.prop, v.areaId); } int64_t makeKey(int32_t prop, int32_t area) const { return (static_cast(prop) << 32) | area; } private: std::vector mConfigs; std::unordered_map mValues; }; class VehicleHalManagerTest : public ::testing::Test { protected: void SetUp() override { hal.reset(new MockedVehicleHal); manager.reset(new VehicleHalManager(hal.get())); objectPool = hal->getValuePool(); } void TearDown() override { manager.reset(nullptr); hal.reset(nullptr); } public: void invokeGet(int32_t property, int32_t areaId) { VehiclePropValue requestedValue {}; requestedValue.prop = property; requestedValue.areaId = areaId; invokeGet(requestedValue); } void invokeGet(const VehiclePropValue& requestedPropValue) { actualValue = VehiclePropValue {}; // reset previous values StatusCode refStatus; VehiclePropValue refValue; bool called = false; manager->get(requestedPropValue, [&refStatus, &refValue, &called] (StatusCode status, const VehiclePropValue& value) { refStatus = status; refValue = value; called = true; }); ASSERT_TRUE(called) << "callback wasn't called for prop: " << hexString(requestedPropValue.prop); actualValue = refValue; actualStatusCode = refStatus; } MockedVehicleHal::VehiclePropValuePtr getComplexProperty() { auto pValue = objectPool->obtainComplex(); pValue->prop = kCustomComplexProperty; pValue->areaId = 0; pValue->value.int32Values = hidl_vec{10, 20}; pValue->value.int64Values = hidl_vec{30, 40}; pValue->value.floatValues = hidl_vec{1.1, 2.2}; pValue->value.bytes = hidl_vec{1, 2, 3}; pValue->value.stringValue = kCarMake; return pValue; } public: VehiclePropValue actualValue; StatusCode actualStatusCode; VehiclePropValuePool* objectPool; std::unique_ptr hal; std::unique_ptr manager; }; TEST_F(VehicleHalManagerTest, getPropConfigs) { hidl_vec properties = { toInt(VehicleProperty::HVAC_FAN_SPEED), toInt(VehicleProperty::INFO_MAKE) }; bool called = false; manager->getPropConfigs(properties, [&called] (StatusCode status, const hidl_vec& c) { ASSERT_EQ(StatusCode::OK, status); ASSERT_EQ(2u, c.size()); called = true; }); ASSERT_TRUE(called); // Verify callback received. called = false; manager->getPropConfigs({ toInt(VehicleProperty::HVAC_FAN_SPEED) }, [&called] (StatusCode status, const hidl_vec& c) { ASSERT_EQ(StatusCode::OK, status); ASSERT_EQ(1u, c.size()); ASSERT_EQ(toString(kVehicleProperties[1]), toString(c[0])); called = true; }); ASSERT_TRUE(called); // Verify callback received. // TODO(pavelm): add case case when property was not declared. } TEST_F(VehicleHalManagerTest, getAllPropConfigs) { bool called = false; manager->getAllPropConfigs( [&called] (const hidl_vec& propConfigs) { ASSERT_EQ(arraysize(kVehicleProperties), propConfigs.size()); for (size_t i = 0; i < propConfigs.size(); i++) { ASSERT_EQ(toString(kVehicleProperties[i]), toString(propConfigs[i])); } called = true; }); ASSERT_TRUE(called); // Verify callback received. } TEST_F(VehicleHalManagerTest, halErrorEvent) { const auto PROP = toInt(VehicleProperty::DISPLAY_BRIGHTNESS); sp cb = new MockedVehicleCallback(); hidl_vec options = { SubscribeOptions{.propId = PROP, .flags = SubscribeFlags::EVENTS_FROM_CAR}, }; StatusCode res = manager->subscribe(cb, options); ASSERT_EQ(StatusCode::OK, res); hal->sendHalError(StatusCode::TRY_AGAIN, PROP, 0 /* area id*/); } TEST_F(VehicleHalManagerTest, subscribe) { const auto PROP = toInt(VehicleProperty::DISPLAY_BRIGHTNESS); sp cb = new MockedVehicleCallback(); hidl_vec options = { SubscribeOptions{.propId = PROP, .flags = SubscribeFlags::EVENTS_FROM_CAR}}; StatusCode res = manager->subscribe(cb, options); ASSERT_EQ(StatusCode::OK, res); auto unsubscribedValue = objectPool->obtain(VehiclePropertyType::INT32); unsubscribedValue->prop = toInt(VehicleProperty::HVAC_FAN_SPEED); hal->sendPropEvent(std::move(unsubscribedValue)); auto& receivedEnvents = cb->getReceivedEvents(); ASSERT_TRUE(cb->waitForExpectedEvents(0)) << " Unexpected events received: " << receivedEnvents.size() << (receivedEnvents.size() > 0 ? toString(receivedEnvents.front()[0]) : ""); auto subscribedValue = objectPool->obtain(VehiclePropertyType::INT32); subscribedValue->prop = PROP; subscribedValue->value.int32Values[0] = 42; cb->reset(); VehiclePropValue actualValue(*subscribedValue.get()); hal->sendPropEvent(std::move(subscribedValue)); ASSERT_TRUE(cb->waitForExpectedEvents(1)) << "Events received: " << receivedEnvents.size(); ASSERT_EQ(toString(actualValue), toString(cb->getReceivedEvents().front()[0])); } TEST_F(VehicleHalManagerTest, subscribe_WriteOnly) { const auto PROP = toInt(VehicleProperty::HVAC_SEAT_TEMPERATURE); sp cb = new MockedVehicleCallback(); hidl_vec options = { SubscribeOptions{.propId = PROP, .flags = SubscribeFlags::EVENTS_FROM_CAR}, }; StatusCode res = manager->subscribe(cb, options); // Unable to subscribe on Hal Events for write-only properties. ASSERT_EQ(StatusCode::INVALID_ARG, res); options[0].flags = SubscribeFlags::EVENTS_FROM_ANDROID; res = manager->subscribe(cb, options); // OK to subscribe on SET method call for write-only properties. ASSERT_EQ(StatusCode::OK, res); } TEST_F(VehicleHalManagerTest, get_Complex) { ASSERT_EQ(StatusCode::OK, hal->set(*getComplexProperty().get())); invokeGet(kCustomComplexProperty, 0); ASSERT_EQ(StatusCode::OK, actualStatusCode); ASSERT_EQ(kCustomComplexProperty, actualValue.prop); ASSERT_EQ(3u, actualValue.value.bytes.size()); ASSERT_EQ(1, actualValue.value.bytes[0]); ASSERT_EQ(2, actualValue.value.bytes[1]); ASSERT_EQ(3, actualValue.value.bytes[2]); ASSERT_EQ(2u, actualValue.value.int32Values.size()); ASSERT_EQ(10, actualValue.value.int32Values[0]); ASSERT_EQ(20, actualValue.value.int32Values[1]); ASSERT_EQ(2u, actualValue.value.floatValues.size()); ASSERT_FLOAT_EQ(1.1, actualValue.value.floatValues[0]); ASSERT_FLOAT_EQ(2.2, actualValue.value.floatValues[1]); ASSERT_EQ(2u, actualValue.value.int64Values.size()); ASSERT_FLOAT_EQ(30, actualValue.value.int64Values[0]); ASSERT_FLOAT_EQ(40, actualValue.value.int64Values[1]); ASSERT_STREQ(kCarMake, actualValue.value.stringValue.c_str()); } TEST_F(VehicleHalManagerTest, get_StaticString) { auto pValue = objectPool->obtainString(kCarMake); pValue->prop = toInt(VehicleProperty::INFO_MAKE); pValue->areaId = 0; ASSERT_EQ(StatusCode::OK, hal->set(*pValue.get())); invokeGet(toInt(VehicleProperty::INFO_MAKE), 0); ASSERT_EQ(StatusCode::OK, actualStatusCode); ASSERT_EQ(toInt(VehicleProperty::INFO_MAKE), actualValue.prop); ASSERT_STREQ(kCarMake, actualValue.value.stringValue.c_str()); } TEST_F(VehicleHalManagerTest, get_NegativeCases) { // Write-only property must fail. invokeGet(toInt(VehicleProperty::HVAC_SEAT_TEMPERATURE), 0); ASSERT_EQ(StatusCode::ACCESS_DENIED, actualStatusCode); // Unknown property must fail. invokeGet(toInt(VehicleProperty::MIRROR_Z_MOVE), 0); ASSERT_EQ(StatusCode::INVALID_ARG, actualStatusCode); } TEST_F(VehicleHalManagerTest, get_Retriable) { actualStatusCode = StatusCode::TRY_AGAIN; int attempts = 0; while (StatusCode::TRY_AGAIN == actualStatusCode && ++attempts < 10) { invokeGet(toInt(VehicleProperty::INFO_FUEL_CAPACITY), 0); } ASSERT_EQ(StatusCode::OK, actualStatusCode); ASSERT_EQ(kRetriablePropMockedAttempts + 1, attempts); ASSERT_FLOAT_EQ(42.42, actualValue.value.floatValues[0]); } TEST_F(VehicleHalManagerTest, set_Basic) { const auto PROP = toInt(VehicleProperty::DISPLAY_BRIGHTNESS); const auto VAL = 7; auto expectedValue = hal->getValuePool()->obtainInt32(VAL); expectedValue->prop = PROP; expectedValue->areaId = 0; actualStatusCode = manager->set(*expectedValue.get()); ASSERT_EQ(StatusCode::OK, actualStatusCode); invokeGet(PROP, 0); ASSERT_EQ(StatusCode::OK, actualStatusCode); ASSERT_EQ(PROP, actualValue.prop); ASSERT_EQ(VAL, actualValue.value.int32Values[0]); } TEST_F(VehicleHalManagerTest, set_DifferentAreas) { const auto PROP = toInt(VehicleProperty::HVAC_FAN_SPEED); const auto VAL1 = 1; const auto VAL2 = 2; const auto AREA1 = toInt(VehicleAreaSeat::ROW_1_LEFT); const auto AREA2 = toInt(VehicleAreaSeat::ROW_1_RIGHT); { auto expectedValue1 = hal->getValuePool()->obtainInt32(VAL1); expectedValue1->prop = PROP; expectedValue1->areaId = AREA1; actualStatusCode = manager->set(*expectedValue1.get()); ASSERT_EQ(StatusCode::OK, actualStatusCode); auto expectedValue2 = hal->getValuePool()->obtainInt32(VAL2); expectedValue2->prop = PROP; expectedValue2->areaId = AREA2; actualStatusCode = manager->set(*expectedValue2.get()); ASSERT_EQ(StatusCode::OK, actualStatusCode); } { invokeGet(PROP, AREA1); ASSERT_EQ(StatusCode::OK, actualStatusCode); ASSERT_EQ(PROP, actualValue.prop); ASSERT_EQ(AREA1, actualValue.areaId); ASSERT_EQ(VAL1, actualValue.value.int32Values[0]); invokeGet(PROP, AREA2); ASSERT_EQ(StatusCode::OK, actualStatusCode); ASSERT_EQ(PROP, actualValue.prop); ASSERT_EQ(AREA2, actualValue.areaId); ASSERT_EQ(VAL2, actualValue.value.int32Values[0]); } } TEST_F(VehicleHalManagerTest, set_Retriable) { const auto PROP = toInt(VehicleProperty::MIRROR_FOLD); auto v = hal->getValuePool()->obtainBoolean(true); v->prop = PROP; v->areaId = 0; actualStatusCode = StatusCode::TRY_AGAIN; int attempts = 0; while (StatusCode::TRY_AGAIN == actualStatusCode && ++attempts < 10) { actualStatusCode = manager->set(*v.get()); } ASSERT_EQ(StatusCode::OK, actualStatusCode); ASSERT_EQ(kRetriablePropMockedAttempts + 1, attempts); invokeGet(PROP, 0); ASSERT_EQ(StatusCode::OK, actualStatusCode); ASSERT_TRUE(actualValue.value.int32Values[0]); } TEST(HalClientVectorTest, basic) { HalClientVector clients; sp callback1 = new MockedVehicleCallback(); sp c1 = new HalClient(callback1); sp c2 = new HalClient(callback1); clients.addOrUpdate(c1); clients.addOrUpdate(c1); clients.addOrUpdate(c2); ASSERT_EQ(2u, clients.size()); ASSERT_FALSE(clients.isEmpty()); ASSERT_LE(0, clients.indexOf(c1)); ASSERT_LE(0, clients.remove(c1)); ASSERT_GT(0, clients.indexOf(c1)); // c1 was already removed ASSERT_GT(0, clients.remove(c1)); // attempt to remove c1 again ASSERT_LE(0, clients.remove(c2)); ASSERT_TRUE(clients.isEmpty()); } TEST_F(VehicleHalManagerTest, debug) { hidl_handle fd = {}; fd.setTo(native_handle_create(/*numFds=*/1, /*numInts=*/0), /*shouldOwn=*/true); // Because debug function returns void, so no way to check return value. manager->debug(fd, {}); manager->debug(fd, {"--help"}); manager->debug(fd, {"--list"}); manager->debug(fd, {"--get"}); manager->debug(fd, {"--set"}); manager->debug(fd, {"invalid"}); } struct SetPropTestCase { std::string test_name; const hidl_vec configs; bool success; }; class VehicleHalManagerSetPropTest : public VehicleHalManagerTest, public testing::WithParamInterface {}; TEST_P(VehicleHalManagerSetPropTest, cmdSetOneProperty) { const SetPropTestCase& tc = GetParam(); VehicleHalManagerTestHelper helper(manager.get()); ASSERT_EQ(tc.success, helper.cmdSetOneProperty(STDERR_FILENO, tc.configs)); } std::vector GenSetPropParams() { char infoMakeProperty[100] = {}; snprintf(infoMakeProperty, sizeof(infoMakeProperty), "%d", toInt(VehicleProperty::INFO_MAKE)); return { {"success_set_string", {"--set", infoMakeProperty, "-s", kCarMake}, true}, {"success_set_bytes", {"--set", infoMakeProperty, "-b", "0xdeadbeef"}, true}, {"success_set_bytes_caps", {"--set", infoMakeProperty, "-b", "0xDEADBEEF"}, true}, {"success_set_int", {"--set", infoMakeProperty, "-i", "2147483647"}, true}, {"success_set_ints", {"--set", infoMakeProperty, "-i", "2147483647", "0", "-2147483648"}, true}, {"success_set_int64", {"--set", infoMakeProperty, "-i64", "-9223372036854775808"}, true}, {"success_set_int64s", {"--set", infoMakeProperty, "-i64", "-9223372036854775808", "0", "9223372036854775807"}, true}, {"success_set_float", {"--set", infoMakeProperty, "-f", "1.175494351E-38"}, true}, {"success_set_floats", {"--set", infoMakeProperty, "-f", "-3.402823466E+38", "0", "3.402823466E+38"}, true}, {"success_set_area", {"--set", infoMakeProperty, "-a", "2147483647"}, true}, {"fail_no_options", {}, false}, {"fail_less_than_4_options", {"--set", infoMakeProperty, "-i"}, false}, {"fail_unknown_options", {"--set", infoMakeProperty, "-s", kCarMake, "-abcd"}, false}, {"fail_invalid_property", {"--set", "not valid", "-s", kCarMake}, false}, {"fail_duplicate_string", {"--set", infoMakeProperty, "-s", kCarMake, "-s", kCarMake}, false}, {"fail_multiple_strings", {"--set", infoMakeProperty, "-s", kCarMake, kCarMake}, false}, {"fail_no_string_value", {"--set", infoMakeProperty, "-s", "-a", "1234"}, false}, {"fail_duplicate_bytes", {"--set", infoMakeProperty, "-b", "0xdeadbeef", "-b", "0xdeadbeef"}, false}, {"fail_multiple_bytes", {"--set", infoMakeProperty, "-b", "0xdeadbeef", "0xdeadbeef"}, false}, {"fail_invalid_bytes", {"--set", infoMakeProperty, "-b", "0xgood"}, false}, {"fail_invalid_bytes_no_prefix", {"--set", infoMakeProperty, "-b", "deadbeef"}, false}, {"fail_invalid_int", {"--set", infoMakeProperty, "-i", "abc"}, false}, {"fail_int_out_of_range", {"--set", infoMakeProperty, "-i", "2147483648"}, false}, {"fail_no_int_value", {"--set", infoMakeProperty, "-i", "-s", kCarMake}, false}, {"fail_invalid_int64", {"--set", infoMakeProperty, "-i64", "abc"}, false}, {"fail_int64_out_of_range", {"--set", infoMakeProperty, "-i64", "-9223372036854775809"}, false}, {"fail_no_int64_value", {"--set", infoMakeProperty, "-i64", "-s", kCarMake}, false}, {"fail_invalid_float", {"--set", infoMakeProperty, "-f", "abc"}, false}, {"fail_float_out_of_range", {"--set", infoMakeProperty, "-f", "-3.402823466E+39"}, false}, {"fail_no_float_value", {"--set", infoMakeProperty, "-f", "-s", kCarMake}, false}, {"fail_multiple_areas", {"--set", infoMakeProperty, "-a", "2147483648", "0"}, false}, {"fail_invalid_area", {"--set", infoMakeProperty, "-a", "abc"}, false}, {"fail_area_out_of_range", {"--set", infoMakeProperty, "-a", "2147483648"}, false}, {"fail_no_area_value", {"--set", infoMakeProperty, "-a", "-s", kCarMake}, false}, }; } INSTANTIATE_TEST_SUITE_P( VehicleHalManagerSetPropTests, VehicleHalManagerSetPropTest, testing::ValuesIn(GenSetPropParams()), [](const testing::TestParamInfo& info) { return info.param.test_name; }); TEST_F(VehicleHalManagerTest, SetComplexPropTest) { char infoMakeProperty[100] = {}; snprintf(infoMakeProperty, sizeof(infoMakeProperty), "%d", toInt(VehicleProperty::INFO_MAKE)); VehicleHalManagerTestHelper helper(manager.get()); ASSERT_TRUE(helper.cmdSetOneProperty( STDERR_FILENO, {"--set", infoMakeProperty, "-s", kCarMake, "-b", "0xdeadbeef", "-i", "2147483647", "0", "-2147483648", "-i64", "-9223372036854775808", "0", "9223372036854775807", "-f", "-3.402823466E+38", "0", "3.402823466E+38", "-a", "123"})); StatusCode status = StatusCode::OK; VehiclePropValue requestProp; requestProp.prop = toInt(VehicleProperty::INFO_MAKE); requestProp.areaId = 123; auto value = hal->get(requestProp, &status); ASSERT_EQ(StatusCode::OK, status); ASSERT_EQ(value->prop, toInt(VehicleProperty::INFO_MAKE)); ASSERT_EQ(value->areaId, 123); ASSERT_STREQ(kCarMake, value->value.stringValue.c_str()); uint8_t bytes[] = {0xde, 0xad, 0xbe, 0xef}; ASSERT_FALSE(memcmp(bytes, value->value.bytes.data(), sizeof(bytes))); ASSERT_EQ(3u, value->value.int32Values.size()); ASSERT_EQ(2147483647, value->value.int32Values[0]); ASSERT_EQ(0, value->value.int32Values[1]); ASSERT_EQ(-2147483648, value->value.int32Values[2]); ASSERT_EQ(3u, value->value.int64Values.size()); // -9223372036854775808 is not a valid literal since '-' and '9223372036854775808' would be two // tokens and the later does not fit in unsigned long long. ASSERT_EQ(-9223372036854775807 - 1, value->value.int64Values[0]); ASSERT_EQ(0, value->value.int64Values[1]); ASSERT_EQ(9223372036854775807, value->value.int64Values[2]); ASSERT_EQ(3u, value->value.floatValues.size()); ASSERT_EQ(-3.402823466E+38f, value->value.floatValues[0]); ASSERT_EQ(0.0f, value->value.floatValues[1]); ASSERT_EQ(3.402823466E+38f, value->value.floatValues[2]); } } // namespace anonymous } // namespace V2_0 } // namespace vehicle } // namespace automotive } // namespace hardware } // namespace android