diff --git a/neuralnetworks/1.1/vts/functional/VtsHalNeuralnetworksV1_1BasicTest.cpp b/neuralnetworks/1.1/vts/functional/VtsHalNeuralnetworksV1_1BasicTest.cpp index 17f6744c2b..10591dcb20 100644 --- a/neuralnetworks/1.1/vts/functional/VtsHalNeuralnetworksV1_1BasicTest.cpp +++ b/neuralnetworks/1.1/vts/functional/VtsHalNeuralnetworksV1_1BasicTest.cpp @@ -286,6 +286,169 @@ TEST_F(NeuralnetworksHidlTest, SimpleExecuteGraphNegativeTest2) { EXPECT_EQ(ErrorStatus::INVALID_ARGUMENT, executionReturnStatus); } +class NeuralnetworksInputsOutputsTest + : public NeuralnetworksHidlTest, + public ::testing::WithParamInterface> { + protected: + virtual void SetUp() { NeuralnetworksHidlTest::SetUp(); } + virtual void TearDown() { NeuralnetworksHidlTest::TearDown(); } + V1_1::Model createModel(const std::vector& inputs, + const std::vector& outputs) { + // We set up the operands as floating-point with no designated + // model inputs and outputs, and then patch type and lifetime + // later on in this function. + + std::vector operands = { + { + .type = OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .numberOfConsumers = 1, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = OperandLifeTime::TEMPORARY_VARIABLE, + .location = {.poolIndex = 0, .offset = 0, .length = 0}, + }, + { + .type = OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .numberOfConsumers = 1, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = OperandLifeTime::TEMPORARY_VARIABLE, + .location = {.poolIndex = 0, .offset = 0, .length = 0}, + }, + { + .type = OperandType::INT32, + .dimensions = {}, + .numberOfConsumers = 1, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = OperandLifeTime::CONSTANT_COPY, + .location = {.poolIndex = 0, .offset = 0, .length = sizeof(int32_t)}, + }, + { + .type = OperandType::TENSOR_FLOAT32, + .dimensions = {1}, + .numberOfConsumers = 0, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = OperandLifeTime::TEMPORARY_VARIABLE, + .location = {.poolIndex = 0, .offset = 0, .length = 0}, + }, + }; + + const std::vector operations = {{ + .type = OperationType::ADD, .inputs = {0, 1, 2}, .outputs = {3}, + }}; + + std::vector operandValues; + int32_t activation[1] = {static_cast(FusedActivationFunc::NONE)}; + operandValues.insert(operandValues.end(), reinterpret_cast(&activation[0]), + reinterpret_cast(&activation[1])); + + if (kQuantized) { + for (auto& operand : operands) { + if (operand.type == OperandType::TENSOR_FLOAT32) { + operand.type = OperandType::TENSOR_QUANT8_ASYMM; + operand.scale = 1.0f; + operand.zeroPoint = 0; + } + } + } + + auto patchLifetime = [&operands](const std::vector& operandIndexes, + OperandLifeTime lifetime) { + for (uint32_t index : operandIndexes) { + operands[index].lifetime = lifetime; + } + }; + if (kInputHasPrecedence) { + patchLifetime(outputs, OperandLifeTime::MODEL_OUTPUT); + patchLifetime(inputs, OperandLifeTime::MODEL_INPUT); + } else { + patchLifetime(inputs, OperandLifeTime::MODEL_INPUT); + patchLifetime(outputs, OperandLifeTime::MODEL_OUTPUT); + } + + return { + .operands = operands, + .operations = operations, + .inputIndexes = inputs, + .outputIndexes = outputs, + .operandValues = operandValues, + .pools = {}, + }; + } + void check(const std::string& name, + bool expectation, // true = success + const std::vector& inputs, const std::vector& outputs) { + SCOPED_TRACE(name + " (HAL calls should " + (expectation ? "succeed" : "fail") + ", " + + (kInputHasPrecedence ? "input" : "output") + " precedence, " + + (kQuantized ? "quantized" : "float")); + + V1_1::Model model = createModel(inputs, outputs); + + // ensure that getSupportedOperations_1_1() checks model validity + ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE; + Return supportedOpsReturn = device->getSupportedOperations_1_1( + 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, + (expectation ? ErrorStatus::NONE : ErrorStatus::INVALID_ARGUMENT)); + + // ensure that prepareModel_1_1() checks model validity + sp preparedModelCallback = new PreparedModelCallback; + ASSERT_NE(preparedModelCallback.get(), nullptr); + Return prepareLaunchReturn = + device->prepareModel_1_1(model, preparedModelCallback); + ASSERT_TRUE(prepareLaunchReturn.isOk()); + ASSERT_TRUE(prepareLaunchReturn == ErrorStatus::NONE || + prepareLaunchReturn == ErrorStatus::INVALID_ARGUMENT); + bool preparationOk = (prepareLaunchReturn == ErrorStatus::NONE); + if (preparationOk) { + preparedModelCallback->wait(); + preparationOk = (preparedModelCallback->getStatus() == ErrorStatus::NONE); + } + + if (preparationOk) { + ASSERT_TRUE(expectation); + } else { + // Preparation can fail for reasons other than an invalid model -- + // for example, perhaps not all operations are supported, or perhaps + // the device hit some kind of capacity limit. + bool invalid = prepareLaunchReturn == ErrorStatus::INVALID_ARGUMENT || + preparedModelCallback->getStatus() == ErrorStatus::INVALID_ARGUMENT; + ASSERT_NE(expectation, invalid); + } + } + + // Indicates whether an operand that appears in both the inputs + // and outputs vector should have lifetime appropriate for input + // rather than for output. + const bool kInputHasPrecedence = std::get<0>(GetParam()); + + // Indicates whether we should test TENSOR_QUANT8_ASYMM rather + // than TENSOR_FLOAT32. + const bool kQuantized = std::get<1>(GetParam()); +}; + +TEST_P(NeuralnetworksInputsOutputsTest, Validate) { + check("Ok", true, {0, 1}, {3}); + check("InputIsOutput", false, {0, 1}, {3, 0}); + check("OutputIsInput", false, {0, 1, 3}, {3}); + check("DuplicateInputs", false, {0, 1, 0}, {3}); + check("DuplicateOutputs", false, {0, 1}, {3, 3}); +} + +INSTANTIATE_TEST_CASE_P(Flavor, NeuralnetworksInputsOutputsTest, + ::testing::Combine(::testing::Bool(), ::testing::Bool())); + } // namespace functional } // namespace vts } // namespace V1_1