/* * Copyright (C) 2018 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. */ #define LOG_TAG "neuralnetworks_hidl_hal_test" #include "VtsHalNeuralnetworks.h" namespace android::hardware::neuralnetworks::V1_2::vts::functional { using implementation::PreparedModelCallback; using V1_0::DeviceStatus; using V1_0::ErrorStatus; using V1_0::OperandLifeTime; using V1_0::PerformanceInfo; using V1_1::ExecutionPreference; using HidlToken = hidl_array(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>; // create device test TEST_P(NeuralnetworksHidlTest, CreateDevice) {} // status test TEST_P(NeuralnetworksHidlTest, StatusTest) { Return status = kDevice->getStatus(); ASSERT_TRUE(status.isOk()); EXPECT_EQ(DeviceStatus::AVAILABLE, static_cast(status)); } // initialization TEST_P(NeuralnetworksHidlTest, GetCapabilitiesTest) { using OperandPerformance = Capabilities::OperandPerformance; Return ret = kDevice->getCapabilities_1_2([](ErrorStatus status, const Capabilities& capabilities) { EXPECT_EQ(ErrorStatus::NONE, status); auto isPositive = [](const PerformanceInfo& perf) { return perf.execTime > 0.0f && perf.powerUsage > 0.0f; }; EXPECT_TRUE(isPositive(capabilities.relaxedFloat32toFloat16PerformanceScalar)); EXPECT_TRUE(isPositive(capabilities.relaxedFloat32toFloat16PerformanceTensor)); const auto& opPerf = capabilities.operandPerformance; EXPECT_TRUE(std::all_of( opPerf.begin(), opPerf.end(), [isPositive](const OperandPerformance& a) { return isPositive(a.info); })); EXPECT_TRUE(std::is_sorted(opPerf.begin(), opPerf.end(), [](const OperandPerformance& a, const OperandPerformance& b) { return a.type < b.type; })); }); EXPECT_TRUE(ret.isOk()); } // device version test TEST_P(NeuralnetworksHidlTest, GetDeviceVersionStringTest) { Return ret = kDevice->getVersionString([](ErrorStatus status, const hidl_string& version) { EXPECT_EQ(ErrorStatus::NONE, status); EXPECT_LT(0, version.size()); }); EXPECT_TRUE(ret.isOk()); } // device type test TEST_P(NeuralnetworksHidlTest, GetDeviceTypeTest) { Return ret = kDevice->getType([](ErrorStatus status, DeviceType type) { EXPECT_EQ(ErrorStatus::NONE, status); EXPECT_TRUE(type == DeviceType::OTHER || type == DeviceType::CPU || type == DeviceType::GPU || type == DeviceType::ACCELERATOR); }); EXPECT_TRUE(ret.isOk()); } // device name test TEST_P(NeuralnetworksHidlTest, GetDeviceNameTest) { const std::string deviceName = getName(GetParam()); auto pos = deviceName.find('-'); EXPECT_NE(pos, std::string::npos); // The separator should not be the first or last character. EXPECT_NE(pos, 0); EXPECT_NE(pos, deviceName.length() - 1); // There should only be 1 separator. EXPECT_EQ(std::string::npos, deviceName.find('-', pos + 1)); } // device supported extensions test TEST_P(NeuralnetworksHidlTest, GetDeviceSupportedExtensionsTest) { Return ret = kDevice->getSupportedExtensions( [](ErrorStatus status, const hidl_vec& extensions) { EXPECT_EQ(ErrorStatus::NONE, status); for (auto& extension : extensions) { std::string extensionName = extension.name; EXPECT_FALSE(extensionName.empty()); for (char c : extensionName) { EXPECT_TRUE(('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || c == '_' || c == '.') << "Extension name contains an illegal character: " << c; } EXPECT_NE(extensionName.find('.'), std::string::npos) << "Extension name must start with the reverse domain name of the " "vendor"; } }); EXPECT_TRUE(ret.isOk()); } // getNumberOfCacheFilesNeeded test TEST_P(NeuralnetworksHidlTest, getNumberOfCacheFilesNeeded) { Return ret = kDevice->getNumberOfCacheFilesNeeded( [](ErrorStatus status, uint32_t numModelCache, uint32_t numDataCache) { EXPECT_EQ(ErrorStatus::NONE, status); EXPECT_LE(numModelCache, static_cast(Constant::MAX_NUMBER_OF_CACHE_FILES)); EXPECT_LE(numDataCache, static_cast(Constant::MAX_NUMBER_OF_CACHE_FILES)); }); EXPECT_TRUE(ret.isOk()); } // detect cycle TEST_P(NeuralnetworksHidlTest, CycleTest) { // opnd0 = TENSOR_FLOAT32 // model input // opnd1 = TENSOR_FLOAT32 // model input // opnd2 = INT32 // model input // opnd3 = ADD(opnd0, opnd4, opnd2) // opnd4 = ADD(opnd1, opnd3, opnd2) // opnd5 = ADD(opnd4, opnd0, opnd2) // model output // // +-----+ // | | // v | // 3 = ADD(0, 4, 2) | // | | // +----------+ | // | | // v | // 4 = ADD(1, 3, 2) | // | | // +----------------+ // | // | // +-------+ // | // v // 5 = ADD(4, 0, 2) const std::vector operands = { { // operands[0] .type = OperandType::TENSOR_FLOAT32, .dimensions = {1}, .numberOfConsumers = 2, .scale = 0.0f, .zeroPoint = 0, .lifetime = OperandLifeTime::MODEL_INPUT, .location = {.poolIndex = 0, .offset = 0, .length = 0}, }, { // operands[1] .type = OperandType::TENSOR_FLOAT32, .dimensions = {1}, .numberOfConsumers = 1, .scale = 0.0f, .zeroPoint = 0, .lifetime = OperandLifeTime::MODEL_INPUT, .location = {.poolIndex = 0, .offset = 0, .length = 0}, }, { // operands[2] .type = OperandType::INT32, .dimensions = {}, .numberOfConsumers = 3, .scale = 0.0f, .zeroPoint = 0, .lifetime = OperandLifeTime::MODEL_INPUT, .location = {.poolIndex = 0, .offset = 0, .length = 0}, }, { // operands[3] .type = OperandType::TENSOR_FLOAT32, .dimensions = {1}, .numberOfConsumers = 1, .scale = 0.0f, .zeroPoint = 0, .lifetime = OperandLifeTime::TEMPORARY_VARIABLE, .location = {.poolIndex = 0, .offset = 0, .length = 0}, }, { // operands[4] .type = OperandType::TENSOR_FLOAT32, .dimensions = {1}, .numberOfConsumers = 2, .scale = 0.0f, .zeroPoint = 0, .lifetime = OperandLifeTime::TEMPORARY_VARIABLE, .location = {.poolIndex = 0, .offset = 0, .length = 0}, }, { // operands[5] .type = OperandType::TENSOR_FLOAT32, .dimensions = {1}, .numberOfConsumers = 0, .scale = 0.0f, .zeroPoint = 0, .lifetime = OperandLifeTime::MODEL_OUTPUT, .location = {.poolIndex = 0, .offset = 0, .length = 0}, }, }; const std::vector operations = { {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}}, {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}}, {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}}, }; const Model model = { .operands = operands, .operations = operations, .inputIndexes = {0, 1, 2}, .outputIndexes = {5}, .operandValues = {}, .pools = {}, }; // ensure that getSupportedOperations_1_2() checks model validity ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE; Return supportedOpsReturn = kDevice->getSupportedOperations_1_2( model, [&model, &supportedOpsErrorStatus](ErrorStatus status, const hidl_vec& supported) { supportedOpsErrorStatus = status; if (status == ErrorStatus::NONE) { ASSERT_EQ(supported.size(), model.operations.size()); } }); ASSERT_TRUE(supportedOpsReturn.isOk()); ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT); // ensure that prepareModel_1_2() checks model validity sp preparedModelCallback = new PreparedModelCallback; Return prepareLaunchReturn = kDevice->prepareModel_1_2( model, ExecutionPreference::FAST_SINGLE_ANSWER, hidl_vec(), hidl_vec(), HidlToken(), preparedModelCallback); ASSERT_TRUE(prepareLaunchReturn.isOk()); // Note that preparation can fail for reasons other than an // invalid model (invalid model should result in // INVALID_ARGUMENT) -- for example, perhaps not all // operations are supported, or perhaps the device hit some // kind of capacity limit. EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE); EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE); EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr); } } // namespace android::hardware::neuralnetworks::V1_2::vts::functional