1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "GeneratedTestHarness.h"
18 
19 #include <aidl/android/hardware/neuralnetworks/ErrorStatus.h>
20 #include <aidl/android/hardware/neuralnetworks/RequestMemoryPool.h>
21 #include <android-base/logging.h>
22 #include <android/binder_auto_utils.h>
23 #include <gtest/gtest.h>
24 
25 #include <algorithm>
26 #include <chrono>
27 #include <iostream>
28 #include <iterator>
29 #include <numeric>
30 #include <vector>
31 
32 #include <android/binder_status.h>
33 #include <nnapi/Result.h>
34 #include <nnapi/SharedMemory.h>
35 #include <nnapi/Types.h>
36 #include <nnapi/hal/aidl/Conversions.h>
37 #include <nnapi/hal/aidl/Utils.h>
38 
39 #include "Callbacks.h"
40 #include "TestHarness.h"
41 #include "Utils.h"
42 #include "VtsHalNeuralnetworks.h"
43 
44 #ifdef __ANDROID__
45 #include <android/sync.h>
46 #endif  // __ANDROID__
47 
48 namespace aidl::android::hardware::neuralnetworks::vts::functional {
49 
50 namespace nn = ::android::nn;
51 using namespace test_helper;
52 using implementation::PreparedModelCallback;
53 
54 namespace {
55 
56 enum class OutputType { FULLY_SPECIFIED, UNSPECIFIED, INSUFFICIENT, MISSED_DEADLINE };
57 
58 struct TestConfig {
59     Executor executor;
60     bool measureTiming;
61     OutputType outputType;
62     MemoryType memoryType;
63     bool reusable;
64     // `reportSkipping` indicates if a test should print an info message in case
65     // it is skipped. The field is set to true by default and is set to false in
66     // quantization coupling tests to suppress skipping a test
67     bool reportSkipping;
68     // `useConfig` indicates if a test should use execute*WithConfig functions for the execution.
69     bool useConfig;
TestConfigaidl::android::hardware::neuralnetworks::vts::functional::__anon4cf4456d0111::TestConfig70     TestConfig(Executor executor, bool measureTiming, OutputType outputType, MemoryType memoryType,
71                bool reusable)
72         : executor(executor),
73           measureTiming(measureTiming),
74           outputType(outputType),
75           memoryType(memoryType),
76           reusable(reusable),
77           reportSkipping(true),
78           useConfig(false) {}
TestConfigaidl::android::hardware::neuralnetworks::vts::functional::__anon4cf4456d0111::TestConfig79     TestConfig(Executor executor, bool measureTiming, OutputType outputType, MemoryType memoryType,
80                bool reusable, bool reportSkipping)
81         : executor(executor),
82           measureTiming(measureTiming),
83           outputType(outputType),
84           memoryType(memoryType),
85           reusable(reusable),
86           reportSkipping(reportSkipping),
87           useConfig(false) {}
TestConfigaidl::android::hardware::neuralnetworks::vts::functional::__anon4cf4456d0111::TestConfig88     TestConfig(Executor executor, bool measureTiming, OutputType outputType, MemoryType memoryType,
89                bool reusable, bool reportSkipping, bool useConfig)
90         : executor(executor),
91           measureTiming(measureTiming),
92           outputType(outputType),
93           memoryType(memoryType),
94           reusable(reusable),
95           reportSkipping(reportSkipping),
96           useConfig(useConfig) {}
97 };
98 
toString(OutputType type)99 std::string toString(OutputType type) {
100     switch (type) {
101         case OutputType::FULLY_SPECIFIED:
102             return "FULLY_SPECIFIED";
103         case OutputType::UNSPECIFIED:
104             return "UNSPECIFIED";
105         case OutputType::INSUFFICIENT:
106             return "INSUFFICIENT";
107         case OutputType::MISSED_DEADLINE:
108             return "MISSED_DEADLINE";
109     }
110 }
111 
toString(const TestConfig & config)112 std::string toString(const TestConfig& config) {
113     std::stringstream ss;
114     ss << "TestConfig{.executor=" << toString(config.executor)
115        << ", .measureTiming=" << (config.measureTiming ? "true" : "false")
116        << ", .outputType=" << toString(config.outputType)
117        << ", .memoryType=" << toString(config.memoryType)
118        << ", .reusable=" << (config.reusable ? "true" : "false")
119        << ", .useConfig=" << (config.useConfig ? "true" : "false") << "}";
120     return ss.str();
121 }
122 
123 enum class IOType { INPUT, OUTPUT };
124 
125 class DeviceMemoryAllocator {
126   public:
DeviceMemoryAllocator(const std::shared_ptr<IDevice> & device,const std::shared_ptr<IPreparedModel> & preparedModel,const TestModel & testModel)127     DeviceMemoryAllocator(const std::shared_ptr<IDevice>& device,
128                           const std::shared_ptr<IPreparedModel>& preparedModel,
129                           const TestModel& testModel)
130         : kDevice(device), kPreparedModel(preparedModel), kTestModel(testModel) {}
131 
132     // Allocate device memory for a target input/output operand.
133     // Return {IBuffer object, token} if successful.
134     // Return {nullptr, 0} if device memory is not supported.
135     template <IOType ioType>
allocate(uint32_t index)136     std::pair<std::shared_ptr<IBuffer>, int32_t> allocate(uint32_t index) {
137         std::pair<std::shared_ptr<IBuffer>, int32_t> buffer;
138         allocateInternal<ioType>(index, &buffer);
139         return buffer;
140     }
141 
142   private:
143     template <IOType ioType>
allocateInternal(int32_t index,std::pair<std::shared_ptr<IBuffer>,int32_t> * result)144     void allocateInternal(int32_t index, std::pair<std::shared_ptr<IBuffer>, int32_t>* result) {
145         ASSERT_NE(result, nullptr);
146 
147         // Prepare arguments.
148         BufferRole role = {.modelIndex = 0, .ioIndex = index, .probability = 1.0f};
149         std::vector<BufferRole> inputRoles, outputRoles;
150         if constexpr (ioType == IOType::INPUT) {
151             inputRoles = {role};
152         } else {
153             outputRoles = {role};
154         }
155 
156         // Allocate device memory.
157         DeviceBuffer buffer;
158         IPreparedModelParcel parcel;
159         parcel.preparedModel = kPreparedModel;
160         const auto ret = kDevice->allocate({}, {parcel}, inputRoles, outputRoles, &buffer);
161 
162         // Check allocation results.
163         if (ret.isOk()) {
164             ASSERT_NE(buffer.buffer, nullptr);
165             ASSERT_GT(buffer.token, 0);
166         } else {
167             ASSERT_EQ(ret.getExceptionCode(), EX_SERVICE_SPECIFIC);
168             ASSERT_EQ(static_cast<ErrorStatus>(ret.getServiceSpecificError()),
169                       ErrorStatus::GENERAL_FAILURE);
170             buffer.buffer = nullptr;
171             buffer.token = 0;
172         }
173 
174         // Initialize input data from TestBuffer.
175         if constexpr (ioType == IOType::INPUT) {
176             if (buffer.buffer != nullptr) {
177                 // TestBuffer -> Shared memory.
178                 const auto& testBuffer =
179                         kTestModel.main.operands[kTestModel.main.inputIndexes[index]].data;
180                 ASSERT_GT(testBuffer.size(), 0);
181                 const auto sharedMemory = nn::createSharedMemory(testBuffer.size()).value();
182                 const auto memory = utils::convert(sharedMemory).value();
183                 const auto mapping = nn::map(sharedMemory).value();
184                 uint8_t* inputPtr = static_cast<uint8_t*>(std::get<void*>(mapping.pointer));
185                 ASSERT_NE(inputPtr, nullptr);
186                 const uint8_t* begin = testBuffer.get<uint8_t>();
187                 const uint8_t* end = begin + testBuffer.size();
188                 std::copy(begin, end, inputPtr);
189 
190                 // Shared memory -> IBuffer.
191                 auto ret = buffer.buffer->copyFrom(memory, {});
192                 ASSERT_TRUE(ret.isOk());
193             }
194         }
195         *result = {std::move(buffer.buffer), buffer.token};
196     }
197 
198     const std::shared_ptr<IDevice> kDevice;
199     const std::shared_ptr<IPreparedModel> kPreparedModel;
200     const TestModel& kTestModel;
201 };
202 
createSubgraph(const TestSubgraph & testSubgraph,uint32_t * constCopySize,std::vector<const TestBuffer * > * constCopies,uint32_t * constRefSize,std::vector<const TestBuffer * > * constReferences)203 Subgraph createSubgraph(const TestSubgraph& testSubgraph, uint32_t* constCopySize,
204                         std::vector<const TestBuffer*>* constCopies, uint32_t* constRefSize,
205                         std::vector<const TestBuffer*>* constReferences) {
206     CHECK(constCopySize != nullptr);
207     CHECK(constCopies != nullptr);
208     CHECK(constRefSize != nullptr);
209     CHECK(constReferences != nullptr);
210 
211     // Operands.
212     std::vector<Operand> operands(testSubgraph.operands.size());
213     for (uint32_t i = 0; i < testSubgraph.operands.size(); i++) {
214         const auto& op = testSubgraph.operands[i];
215 
216         DataLocation loc = {};
217         if (op.lifetime == TestOperandLifeTime::CONSTANT_COPY) {
218             loc = {
219                     .poolIndex = 0,
220                     .offset = *constCopySize,
221                     .length = static_cast<int64_t>(op.data.size()),
222             };
223             constCopies->push_back(&op.data);
224             *constCopySize += op.data.alignedSize();
225         } else if (op.lifetime == TestOperandLifeTime::CONSTANT_REFERENCE) {
226             loc = {
227                     .poolIndex = 0,
228                     .offset = *constRefSize,
229                     .length = static_cast<int64_t>(op.data.size()),
230             };
231             constReferences->push_back(&op.data);
232             *constRefSize += op.data.alignedSize();
233         } else if (op.lifetime == TestOperandLifeTime::SUBGRAPH) {
234             loc = {
235                     .poolIndex = 0,
236                     .offset = *op.data.get<uint32_t>(),
237                     .length = 0,
238             };
239         }
240 
241         std::optional<OperandExtraParams> extraParams;
242         if (op.type == TestOperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) {
243             using Tag = OperandExtraParams::Tag;
244             extraParams = OperandExtraParams::make<Tag::channelQuant>(SymmPerChannelQuantParams{
245                     .scales = op.channelQuant.scales,
246                     .channelDim = static_cast<int32_t>(op.channelQuant.channelDim)});
247         }
248 
249         operands[i] = {.type = static_cast<OperandType>(op.type),
250                        .dimensions = utils::toSigned(op.dimensions).value(),
251                        .scale = op.scale,
252                        .zeroPoint = op.zeroPoint,
253                        .lifetime = static_cast<OperandLifeTime>(op.lifetime),
254                        .location = loc,
255                        .extraParams = std::move(extraParams)};
256     }
257 
258     // Operations.
259     std::vector<Operation> operations(testSubgraph.operations.size());
260     std::transform(testSubgraph.operations.begin(), testSubgraph.operations.end(),
261                    operations.begin(), [](const TestOperation& op) -> Operation {
262                        return {.type = static_cast<OperationType>(op.type),
263                                .inputs = utils::toSigned(op.inputs).value(),
264                                .outputs = utils::toSigned(op.outputs).value()};
265                    });
266 
267     return {.operands = std::move(operands),
268             .operations = std::move(operations),
269             .inputIndexes = utils::toSigned(testSubgraph.inputIndexes).value(),
270             .outputIndexes = utils::toSigned(testSubgraph.outputIndexes).value()};
271 }
272 
copyTestBuffers(const std::vector<const TestBuffer * > & buffers,uint8_t * output)273 void copyTestBuffers(const std::vector<const TestBuffer*>& buffers, uint8_t* output) {
274     uint32_t offset = 0;
275     for (const TestBuffer* buffer : buffers) {
276         const uint8_t* begin = buffer->get<uint8_t>();
277         const uint8_t* end = begin + buffer->size();
278         std::copy(begin, end, output + offset);
279         offset += buffer->alignedSize();
280     }
281 }
282 
283 }  // namespace
284 
waitForSyncFence(int syncFd)285 void waitForSyncFence(int syncFd) {
286     ASSERT_GT(syncFd, 0);
287 #ifdef __ANDROID__
288     constexpr int kInfiniteTimeout = -1;
289     int r = sync_wait(syncFd, kInfiniteTimeout);
290     ASSERT_GE(r, 0);
291 #else   // __ANDROID__
292     LOG(FATAL) << "waitForSyncFence not supported on host";
293 #endif  // __ANDROID__
294 }
295 
createModel(const TestModel & testModel)296 Model createModel(const TestModel& testModel) {
297     uint32_t constCopySize = 0;
298     uint32_t constRefSize = 0;
299     std::vector<const TestBuffer*> constCopies;
300     std::vector<const TestBuffer*> constReferences;
301 
302     Subgraph mainSubgraph = createSubgraph(testModel.main, &constCopySize, &constCopies,
303                                            &constRefSize, &constReferences);
304     std::vector<Subgraph> refSubgraphs(testModel.referenced.size());
305     std::transform(testModel.referenced.begin(), testModel.referenced.end(), refSubgraphs.begin(),
306                    [&constCopySize, &constCopies, &constRefSize,
307                     &constReferences](const TestSubgraph& testSubgraph) {
308                        return createSubgraph(testSubgraph, &constCopySize, &constCopies,
309                                              &constRefSize, &constReferences);
310                    });
311 
312     // Constant copies.
313     std::vector<uint8_t> operandValues(constCopySize);
314     copyTestBuffers(constCopies, operandValues.data());
315 
316     // Shared memory.
317     std::vector<nn::SharedMemory> pools = {};
318     if (constRefSize > 0) {
319         const auto pool = nn::createSharedMemory(constRefSize).value();
320         pools.push_back(pool);
321 
322         // load data
323         const auto mappedMemory = nn::map(pool).value();
324         uint8_t* mappedPtr = static_cast<uint8_t*>(std::get<void*>(mappedMemory.pointer));
325         CHECK(mappedPtr != nullptr);
326 
327         copyTestBuffers(constReferences, mappedPtr);
328     }
329 
330     std::vector<Memory> aidlPools;
331     aidlPools.reserve(pools.size());
332     for (auto& pool : pools) {
333         auto aidlPool = utils::convert(pool).value();
334         aidlPools.push_back(std::move(aidlPool));
335     }
336 
337     return {.main = std::move(mainSubgraph),
338             .referenced = std::move(refSubgraphs),
339             .operandValues = std::move(operandValues),
340             .pools = std::move(aidlPools),
341             .relaxComputationFloat32toFloat16 = testModel.isRelaxed};
342 }
343 
isOutputSizeGreaterThanOne(const TestModel & testModel,uint32_t index)344 static bool isOutputSizeGreaterThanOne(const TestModel& testModel, uint32_t index) {
345     const auto byteSize = testModel.main.operands[testModel.main.outputIndexes[index]].data.size();
346     return byteSize > 1u;
347 }
348 
makeOutputInsufficientSize(uint32_t outputIndex,Request * request)349 static void makeOutputInsufficientSize(uint32_t outputIndex, Request* request) {
350     auto& loc = request->outputs[outputIndex].location;
351     ASSERT_GT(loc.length, 1u);
352     loc.length -= 1u;
353     // Test that the padding is not used for output data.
354     loc.padding += 1u;
355 }
356 
makeOutputDimensionsUnspecified(Model * model)357 static void makeOutputDimensionsUnspecified(Model* model) {
358     for (auto i : model->main.outputIndexes) {
359         auto& dims = model->main.operands[i].dimensions;
360         std::fill(dims.begin(), dims.end(), 0);
361     }
362 }
363 
364 // Manages the lifetime of memory resources used in an execution.
365 class ExecutionContext {
366   public:
ExecutionContext(std::shared_ptr<IDevice> device,std::shared_ptr<IPreparedModel> preparedModel)367     ExecutionContext(std::shared_ptr<IDevice> device, std::shared_ptr<IPreparedModel> preparedModel)
368         : kDevice(std::move(device)), kPreparedModel(std::move(preparedModel)) {}
369 
370     std::optional<Request> createRequest(const TestModel& testModel, MemoryType memoryType);
371     std::vector<TestBuffer> getOutputBuffers(const TestModel& testModel,
372                                              const Request& request) const;
373 
374   private:
375     // Get a TestBuffer with data copied from an IBuffer object.
376     void getBuffer(const std::shared_ptr<IBuffer>& buffer, size_t size,
377                    TestBuffer* testBuffer) const;
378 
379     static constexpr uint32_t kInputPoolIndex = 0;
380     static constexpr uint32_t kOutputPoolIndex = 1;
381     static constexpr uint32_t kDeviceMemoryBeginIndex = 2;
382 
383     const std::shared_ptr<IDevice> kDevice;
384     const std::shared_ptr<IPreparedModel> kPreparedModel;
385     std::unique_ptr<TestMemoryBase> mInputMemory, mOutputMemory;
386     std::vector<std::shared_ptr<IBuffer>> mBuffers;
387 };
388 
389 // Returns the number of bytes needed to round up "size" to the nearest multiple of "multiple".
roundUpBytesNeeded(uint32_t size,uint32_t multiple)390 static uint32_t roundUpBytesNeeded(uint32_t size, uint32_t multiple) {
391     CHECK(multiple != 0);
392     return ((size + multiple - 1) / multiple) * multiple - size;
393 }
394 
createRequest(const TestModel & testModel,MemoryType memoryType)395 std::optional<Request> ExecutionContext::createRequest(const TestModel& testModel,
396                                                        MemoryType memoryType) {
397     // Memory pools are organized as:
398     // - 0: Input shared memory pool
399     // - 1: Output shared memory pool
400     // - [2, 2+i): Input device memories
401     // - [2+i, 2+i+o): Output device memories
402     DeviceMemoryAllocator allocator(kDevice, kPreparedModel, testModel);
403     std::vector<int32_t> tokens;
404     mBuffers.clear();
405 
406     // Model inputs.
407     std::vector<RequestArgument> inputs(testModel.main.inputIndexes.size());
408     size_t inputSize = 0;
409     for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
410         const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
411         if (op.data.size() == 0) {
412             // Omitted input.
413             inputs[i] = {.hasNoValue = true};
414             continue;
415         } else if (memoryType == MemoryType::DEVICE) {
416             SCOPED_TRACE("Input index = " + std::to_string(i));
417             auto [buffer, token] = allocator.allocate<IOType::INPUT>(i);
418             if (buffer != nullptr) {
419                 DataLocation loc = {.poolIndex = static_cast<int32_t>(mBuffers.size() +
420                                                                       kDeviceMemoryBeginIndex)};
421                 mBuffers.push_back(std::move(buffer));
422                 tokens.push_back(token);
423                 inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
424                 continue;
425             }
426         }
427 
428         // Reserve shared memory for input.
429         inputSize += roundUpBytesNeeded(inputSize, nn::kDefaultRequestMemoryAlignment);
430         const auto padding = roundUpBytesNeeded(op.data.size(), nn::kDefaultRequestMemoryPadding);
431         DataLocation loc = {.poolIndex = kInputPoolIndex,
432                             .offset = static_cast<int64_t>(inputSize),
433                             .length = static_cast<int64_t>(op.data.size()),
434                             .padding = static_cast<int64_t>(padding)};
435         inputSize += (op.data.size() + padding);
436         inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
437     }
438 
439     // Model outputs.
440     std::vector<RequestArgument> outputs(testModel.main.outputIndexes.size());
441     size_t outputSize = 0;
442     for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
443         const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
444         if (memoryType == MemoryType::DEVICE) {
445             SCOPED_TRACE("Output index = " + std::to_string(i));
446             auto [buffer, token] = allocator.allocate<IOType::OUTPUT>(i);
447             if (buffer != nullptr) {
448                 DataLocation loc = {.poolIndex = static_cast<int32_t>(mBuffers.size() +
449                                                                       kDeviceMemoryBeginIndex)};
450                 mBuffers.push_back(std::move(buffer));
451                 tokens.push_back(token);
452                 outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
453                 continue;
454             }
455         }
456 
457         // In the case of zero-sized output, we should at least provide a one-byte buffer.
458         // This is because zero-sized tensors are only supported internally to the driver, or
459         // reported in output shapes. It is illegal for the client to pre-specify a zero-sized
460         // tensor as model output. Otherwise, we will have two semantic conflicts:
461         // - "Zero dimension" conflicts with "unspecified dimension".
462         // - "Omitted operand buffer" conflicts with "zero-sized operand buffer".
463         size_t bufferSize = std::max<size_t>(op.data.size(), 1);
464 
465         // Reserve shared memory for output.
466         outputSize += roundUpBytesNeeded(outputSize, nn::kDefaultRequestMemoryAlignment);
467         const auto padding = roundUpBytesNeeded(bufferSize, nn::kDefaultRequestMemoryPadding);
468         DataLocation loc = {.poolIndex = kOutputPoolIndex,
469                             .offset = static_cast<int64_t>(outputSize),
470                             .length = static_cast<int64_t>(bufferSize),
471                             .padding = static_cast<int64_t>(padding)};
472         outputSize += (bufferSize + padding);
473         outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
474     }
475 
476     if (memoryType == MemoryType::DEVICE && mBuffers.empty()) {
477         return std::nullopt;
478     }
479 
480     // Memory pools.
481     if (memoryType == MemoryType::BLOB_AHWB) {
482         mInputMemory = TestBlobAHWB::create(std::max<size_t>(inputSize, 1));
483         mOutputMemory = TestBlobAHWB::create(std::max<size_t>(outputSize, 1));
484     } else {
485         mInputMemory = TestAshmem::create(std::max<size_t>(inputSize, 1), /*aidlReadonly=*/true);
486         mOutputMemory = TestAshmem::create(std::max<size_t>(outputSize, 1), /*aidlReadonly=*/false);
487     }
488     CHECK_NE(mInputMemory, nullptr);
489     CHECK_NE(mOutputMemory, nullptr);
490     std::vector<RequestMemoryPool> pools;
491     pools.reserve(kDeviceMemoryBeginIndex + mBuffers.size());
492 
493     auto copiedInputMemory = utils::clone(*mInputMemory->getAidlMemory());
494     CHECK(copiedInputMemory.has_value()) << copiedInputMemory.error().message;
495     auto copiedOutputMemory = utils::clone(*mOutputMemory->getAidlMemory());
496     CHECK(copiedOutputMemory.has_value()) << copiedOutputMemory.error().message;
497 
498     pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
499             std::move(copiedInputMemory).value()));
500     pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::pool>(
501             std::move(copiedOutputMemory).value()));
502     for (const auto& token : tokens) {
503         pools.push_back(RequestMemoryPool::make<RequestMemoryPool::Tag::token>(token));
504     }
505 
506     // Copy input data to the input shared memory pool.
507     uint8_t* inputPtr = mInputMemory->getPointer();
508     for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
509         if (!inputs[i].hasNoValue && inputs[i].location.poolIndex == kInputPoolIndex) {
510             const auto& op = testModel.main.operands[testModel.main.inputIndexes[i]];
511             const uint8_t* begin = op.data.get<uint8_t>();
512             const uint8_t* end = begin + op.data.size();
513             std::copy(begin, end, inputPtr + inputs[i].location.offset);
514         }
515     }
516     return Request{
517             .inputs = std::move(inputs), .outputs = std::move(outputs), .pools = std::move(pools)};
518 }
519 
getOutputBuffers(const TestModel & testModel,const Request & request) const520 std::vector<TestBuffer> ExecutionContext::getOutputBuffers(const TestModel& testModel,
521                                                            const Request& request) const {
522     // Copy out output results.
523     uint8_t* outputPtr = mOutputMemory->getPointer();
524     std::vector<TestBuffer> outputBuffers;
525     for (uint32_t i = 0; i < request.outputs.size(); i++) {
526         const auto& outputLoc = request.outputs[i].location;
527         if (outputLoc.poolIndex == kOutputPoolIndex) {
528             outputBuffers.emplace_back(outputLoc.length, outputPtr + outputLoc.offset);
529         } else {
530             const auto& op = testModel.main.operands[testModel.main.outputIndexes[i]];
531             if (op.data.size() == 0) {
532                 outputBuffers.emplace_back(0, nullptr);
533             } else {
534                 SCOPED_TRACE("Output index = " + std::to_string(i));
535                 const uint32_t bufferIndex = outputLoc.poolIndex - kDeviceMemoryBeginIndex;
536                 TestBuffer buffer;
537                 getBuffer(mBuffers[bufferIndex], op.data.size(), &buffer);
538                 outputBuffers.push_back(std::move(buffer));
539             }
540         }
541     }
542     return outputBuffers;
543 }
544 
545 // Get a TestBuffer with data copied from an IBuffer object.
getBuffer(const std::shared_ptr<IBuffer> & buffer,size_t size,TestBuffer * testBuffer) const546 void ExecutionContext::getBuffer(const std::shared_ptr<IBuffer>& buffer, size_t size,
547                                  TestBuffer* testBuffer) const {
548     // IBuffer -> Shared memory.
549     auto sharedMemory = nn::createSharedMemory(size).value();
550     auto aidlMemory = utils::convert(sharedMemory).value();
551     const auto ret = buffer->copyTo(aidlMemory);
552     ASSERT_TRUE(ret.isOk());
553 
554     // Shared memory -> TestBuffer.
555     const auto outputMemory = nn::map(sharedMemory).value();
556     const uint8_t* outputPtr = std::visit(
557             [](auto* ptr) { return static_cast<const uint8_t*>(ptr); }, outputMemory.pointer);
558     ASSERT_NE(outputPtr, nullptr);
559     ASSERT_NE(testBuffer, nullptr);
560     *testBuffer = TestBuffer(size, outputPtr);
561 }
562 
hasZeroSizedOutput(const TestModel & testModel)563 static bool hasZeroSizedOutput(const TestModel& testModel) {
564     return std::any_of(testModel.main.outputIndexes.begin(), testModel.main.outputIndexes.end(),
565                        [&testModel](uint32_t index) {
566                            return testModel.main.operands[index].data.size() == 0;
567                        });
568 }
569 
EvaluatePreparedModel(const std::shared_ptr<IDevice> & device,const std::shared_ptr<IPreparedModel> & preparedModel,const TestModel & testModel,const TestConfig & testConfig,bool * skipped=nullptr)570 void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
571                            const std::shared_ptr<IPreparedModel>& preparedModel,
572                            const TestModel& testModel, const TestConfig& testConfig,
573                            bool* skipped = nullptr) {
574     if (skipped != nullptr) {
575         *skipped = false;
576     }
577     // If output0 does not have size larger than one byte, we can not test with insufficient buffer.
578     if (testConfig.outputType == OutputType::INSUFFICIENT &&
579         !isOutputSizeGreaterThanOne(testModel, 0)) {
580         return;
581     }
582 
583     ExecutionContext context(device, preparedModel);
584     auto maybeRequest = context.createRequest(testModel, testConfig.memoryType);
585     // Skip if testing memory domain but no device memory has been allocated.
586     if (!maybeRequest.has_value()) {
587         return;
588     }
589 
590     Request request = std::move(maybeRequest).value();
591 
592     constexpr uint32_t kInsufficientOutputIndex = 0;
593     if (testConfig.outputType == OutputType::INSUFFICIENT) {
594         makeOutputInsufficientSize(kInsufficientOutputIndex, &request);
595     }
596 
597     int64_t loopTimeoutDurationNs = kOmittedTimeoutDuration;
598     // OutputType::MISSED_DEADLINE is only used by
599     // TestKind::INTINITE_LOOP_TIMEOUT tests to verify that an infinite loop is
600     // aborted after a timeout.
601     if (testConfig.outputType == OutputType::MISSED_DEADLINE) {
602         // Override the default loop timeout duration with a small value to
603         // speed up test execution.
604         constexpr int64_t kMillisecond = 1'000'000;
605         loopTimeoutDurationNs = 1 * kMillisecond;
606     }
607 
608     std::shared_ptr<IExecution> execution;
609     if (testConfig.reusable) {
610         const auto ret = preparedModel->createReusableExecution(
611                 request, {testConfig.measureTiming, loopTimeoutDurationNs, {}, {}}, &execution);
612         ASSERT_TRUE(ret.isOk()) << static_cast<nn::ErrorStatus>(ret.getServiceSpecificError());
613         ASSERT_NE(nullptr, execution.get());
614     }
615 
616     const auto executeAndCheckResults = [&preparedModel, &execution, &testConfig, &testModel,
617                                          &context, &request, loopTimeoutDurationNs, skipped]() {
618         ErrorStatus executionStatus;
619         std::vector<OutputShape> outputShapes;
620         Timing timing = kNoTiming;
621         switch (testConfig.executor) {
622             case Executor::SYNC: {
623                 SCOPED_TRACE("synchronous");
624 
625                 ExecutionResult executionResult;
626                 // execute
627                 ::ndk::ScopedAStatus ret;
628                 if (testConfig.reusable) {
629                     ret = execution->executeSynchronously(kNoDeadline, &executionResult);
630                 } else if (testConfig.useConfig) {
631                     ret = preparedModel->executeSynchronouslyWithConfig(
632                             request, {testConfig.measureTiming, loopTimeoutDurationNs, {}, {}},
633                             kNoDeadline, &executionResult);
634                 } else {
635                     ret = preparedModel->executeSynchronously(request, testConfig.measureTiming,
636                                                               kNoDeadline, loopTimeoutDurationNs,
637                                                               &executionResult);
638                 }
639                 ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
640                         << ret.getDescription();
641                 if (ret.isOk()) {
642                     executionStatus = executionResult.outputSufficientSize
643                                               ? ErrorStatus::NONE
644                                               : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE;
645                     outputShapes = std::move(executionResult.outputShapes);
646                     timing = executionResult.timing;
647                 } else {
648                     executionStatus = static_cast<ErrorStatus>(ret.getServiceSpecificError());
649                 }
650                 break;
651             }
652             case Executor::BURST: {
653                 SCOPED_TRACE("burst");
654 
655                 // create burst
656                 std::shared_ptr<IBurst> burst;
657                 auto ret = preparedModel->configureExecutionBurst(&burst);
658                 ASSERT_TRUE(ret.isOk()) << ret.getDescription();
659                 ASSERT_NE(nullptr, burst.get());
660 
661                 // associate a unique slot with each memory pool
662                 constexpr int64_t kIgnoreSlot = -1;
663                 int64_t currentSlot = 0;
664                 std::vector<int64_t> slots;
665                 slots.reserve(request.pools.size());
666                 for (const auto& pool : request.pools) {
667                     if (pool.getTag() == RequestMemoryPool::Tag::pool) {
668                         slots.push_back(currentSlot++);
669                     } else {
670                         EXPECT_EQ(pool.getTag(), RequestMemoryPool::Tag::token);
671                         slots.push_back(kIgnoreSlot);
672                     }
673                 }
674 
675                 ExecutionResult executionResult;
676                 // execute
677                 if (testConfig.useConfig) {
678                     ret = burst->executeSynchronouslyWithConfig(
679                             request, slots,
680                             {testConfig.measureTiming, loopTimeoutDurationNs, {}, {}}, kNoDeadline,
681                             &executionResult);
682                 } else {
683                     ret = burst->executeSynchronously(request, slots, testConfig.measureTiming,
684                                                       kNoDeadline, loopTimeoutDurationNs,
685                                                       &executionResult);
686                 }
687                 ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
688                         << ret.getDescription();
689                 if (ret.isOk()) {
690                     executionStatus = executionResult.outputSufficientSize
691                                               ? ErrorStatus::NONE
692                                               : ErrorStatus::OUTPUT_INSUFFICIENT_SIZE;
693                     outputShapes = std::move(executionResult.outputShapes);
694                     timing = executionResult.timing;
695                 } else {
696                     executionStatus = static_cast<ErrorStatus>(ret.getServiceSpecificError());
697                 }
698 
699                 // Mark each slot as unused after the execution. This is unnecessary because the
700                 // burst is freed after this scope ends, but this is here to test the functionality.
701                 for (int64_t slot : slots) {
702                     if (slot != kIgnoreSlot) {
703                         ret = burst->releaseMemoryResource(slot);
704                         ASSERT_TRUE(ret.isOk()) << ret.getDescription();
705                     }
706                 }
707 
708                 break;
709             }
710             case Executor::FENCED: {
711                 SCOPED_TRACE("fenced");
712                 ErrorStatus result = ErrorStatus::NONE;
713                 FencedExecutionResult executionResult;
714                 ::ndk::ScopedAStatus ret;
715                 if (testConfig.reusable) {
716                     ret = execution->executeFenced({}, kNoDeadline, kNoDuration, &executionResult);
717                 } else if (testConfig.useConfig) {
718                     ret = preparedModel->executeFencedWithConfig(
719                             request, {}, {testConfig.measureTiming, loopTimeoutDurationNs, {}, {}},
720                             kNoDeadline, kNoDuration, &executionResult);
721                 } else {
722                     ret = preparedModel->executeFenced(request, {}, testConfig.measureTiming,
723                                                        kNoDeadline, loopTimeoutDurationNs,
724                                                        kNoDuration, &executionResult);
725                 }
726                 ASSERT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
727                         << ret.getDescription();
728                 if (!ret.isOk()) {
729                     result = static_cast<ErrorStatus>(ret.getServiceSpecificError());
730                     executionStatus = result;
731                 } else if (executionResult.syncFence.get() != -1) {
732                     std::vector<ndk::ScopedFileDescriptor> waitFor;
733                     auto dupFd = dup(executionResult.syncFence.get());
734                     ASSERT_NE(dupFd, -1);
735                     waitFor.emplace_back(dupFd);
736                     // If a sync fence is returned, try start another run waiting for the sync
737                     // fence.
738                     if (testConfig.reusable) {
739                         // Nothing to do because at most one execution may occur on a reusable
740                         // execution object at any given time.
741                     } else if (testConfig.useConfig) {
742                         ret = preparedModel->executeFencedWithConfig(
743                                 request, waitFor,
744                                 {testConfig.measureTiming, loopTimeoutDurationNs, {}, {}},
745                                 kNoDeadline, kNoDuration, &executionResult);
746                     } else {
747                         ret = preparedModel->executeFenced(
748                                 request, waitFor, testConfig.measureTiming, kNoDeadline,
749                                 loopTimeoutDurationNs, kNoDuration, &executionResult);
750                     }
751                     ASSERT_TRUE(ret.isOk());
752                     waitForSyncFence(executionResult.syncFence.get());
753                 }
754                 if (result == ErrorStatus::NONE) {
755                     ASSERT_NE(executionResult.callback, nullptr);
756                     Timing timingFenced;
757                     auto ret = executionResult.callback->getExecutionInfo(&timing, &timingFenced,
758                                                                           &executionStatus);
759                     ASSERT_TRUE(ret.isOk());
760                 }
761                 break;
762             }
763             default: {
764                 FAIL() << "Unsupported execution mode for AIDL interface.";
765             }
766         }
767 
768         if (testConfig.outputType != OutputType::FULLY_SPECIFIED &&
769             executionStatus == ErrorStatus::GENERAL_FAILURE) {
770             if (skipped != nullptr) {
771                 *skipped = true;
772             }
773             if (!testConfig.reportSkipping) {
774                 return;
775             }
776             LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
777                          "execute model that it does not support.";
778             std::cout << "[          ]   Early termination of test because vendor service cannot "
779                          "execute model that it does not support."
780                       << std::endl;
781             GTEST_SKIP();
782         }
783         if (!testConfig.measureTiming) {
784             EXPECT_EQ(timing, kNoTiming);
785         } else {
786             if (timing.timeOnDeviceNs != -1 && timing.timeInDriverNs != -1) {
787                 EXPECT_LE(timing.timeOnDeviceNs, timing.timeInDriverNs);
788             }
789         }
790 
791         switch (testConfig.outputType) {
792             case OutputType::FULLY_SPECIFIED:
793                 if (testConfig.executor == Executor::FENCED && hasZeroSizedOutput(testModel)) {
794                     // Executor::FENCED does not support zero-sized output.
795                     ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
796                     return;
797                 }
798                 // If the model output operands are fully specified, outputShapes must be either
799                 // either empty, or have the same number of elements as the number of outputs.
800                 ASSERT_EQ(ErrorStatus::NONE, executionStatus);
801                 ASSERT_TRUE(outputShapes.size() == 0 ||
802                             outputShapes.size() == testModel.main.outputIndexes.size());
803                 break;
804             case OutputType::UNSPECIFIED:
805                 if (testConfig.executor == Executor::FENCED) {
806                     // For Executor::FENCED, the output shape must be fully specified.
807                     ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
808                     return;
809                 }
810                 // If the model output operands are not fully specified, outputShapes must have
811                 // the same number of elements as the number of outputs.
812                 ASSERT_EQ(ErrorStatus::NONE, executionStatus);
813                 ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size());
814                 break;
815             case OutputType::INSUFFICIENT:
816                 if (testConfig.executor == Executor::FENCED) {
817                     // For Executor::FENCED, the output shape must be fully specified.
818                     ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionStatus);
819                     return;
820                 }
821                 ASSERT_EQ(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, executionStatus);
822                 ASSERT_EQ(outputShapes.size(), testModel.main.outputIndexes.size());
823                 // Check that all returned output dimensions are at least as fully specified as the
824                 // union of the information about the corresponding operand in the model and in the
825                 // request. In this test, all model outputs have known rank with all dimensions
826                 // unspecified, and no dimensional information is provided in the request.
827                 for (uint32_t i = 0; i < outputShapes.size(); i++) {
828                     ASSERT_EQ(outputShapes[i].isSufficient, i != kInsufficientOutputIndex);
829                     const auto& actual = outputShapes[i].dimensions;
830                     const auto& golden =
831                             testModel.main.operands[testModel.main.outputIndexes[i]].dimensions;
832                     ASSERT_EQ(actual.size(), golden.size());
833                     for (uint32_t j = 0; j < actual.size(); j++) {
834                         if (actual[j] == 0) continue;
835                         EXPECT_EQ(actual[j], golden[j]) << "index: " << j;
836                     }
837                 }
838                 return;
839             case OutputType::MISSED_DEADLINE:
840                 ASSERT_TRUE(executionStatus == ErrorStatus::MISSED_DEADLINE_TRANSIENT ||
841                             executionStatus == ErrorStatus::MISSED_DEADLINE_PERSISTENT)
842                         << "executionStatus = " << executionStatus;
843                 return;
844         }
845 
846         // Go through all outputs, check returned output shapes.
847         for (uint32_t i = 0; i < outputShapes.size(); i++) {
848             EXPECT_TRUE(outputShapes[i].isSufficient);
849             const auto& expect =
850                     testModel.main.operands[testModel.main.outputIndexes[i]].dimensions;
851             const auto unsignedActual = nn::toUnsigned(outputShapes[i].dimensions);
852             ASSERT_TRUE(unsignedActual.has_value());
853             const std::vector<uint32_t>& actual = unsignedActual.value();
854             EXPECT_EQ(expect, actual);
855         }
856 
857         // Retrieve execution results.
858         const std::vector<TestBuffer> outputs = context.getOutputBuffers(testModel, request);
859 
860         // We want "close-enough" results.
861         checkResults(testModel, outputs);
862     };
863 
864     executeAndCheckResults();
865 
866     // For reusable execution tests, run the execution twice.
867     if (testConfig.reusable) {
868         SCOPED_TRACE("Second execution");
869         executeAndCheckResults();
870     }
871 }
872 
EvaluatePreparedModel(const std::shared_ptr<IDevice> & device,const std::shared_ptr<IPreparedModel> & preparedModel,const TestModel & testModel,TestKind testKind)873 void EvaluatePreparedModel(const std::shared_ptr<IDevice>& device,
874                            const std::shared_ptr<IPreparedModel>& preparedModel,
875                            const TestModel& testModel, TestKind testKind) {
876     std::vector<OutputType> outputTypesList;
877     std::vector<bool> measureTimingList;
878     std::vector<Executor> executorList;
879     std::vector<MemoryType> memoryTypeList;
880     std::vector<bool> reusableList = {false};
881     std::vector<bool> useConfigList = {false};
882 
883     int deviceVersion;
884     ASSERT_TRUE(device->getInterfaceVersion(&deviceVersion).isOk());
885     if (deviceVersion >= kMinAidlLevelForFL8) {
886         reusableList.push_back(true);
887         useConfigList.push_back(true);
888     }
889 
890     switch (testKind) {
891         case TestKind::GENERAL: {
892             outputTypesList = {OutputType::FULLY_SPECIFIED};
893             measureTimingList = {false, true};
894             executorList = {Executor::SYNC, Executor::BURST};
895             memoryTypeList = {MemoryType::ASHMEM};
896         } break;
897         case TestKind::DYNAMIC_SHAPE: {
898             outputTypesList = {OutputType::UNSPECIFIED, OutputType::INSUFFICIENT};
899             measureTimingList = {false, true};
900             executorList = {Executor::SYNC, Executor::BURST, Executor::FENCED};
901             memoryTypeList = {MemoryType::ASHMEM};
902         } break;
903         case TestKind::MEMORY_DOMAIN: {
904             outputTypesList = {OutputType::FULLY_SPECIFIED};
905             measureTimingList = {false};
906             executorList = {Executor::SYNC, Executor::BURST, Executor::FENCED};
907 #ifdef __ANDROID__
908             memoryTypeList = {MemoryType::BLOB_AHWB, MemoryType::DEVICE};
909 #else   // __ANDROID__
910             memoryTypeList = {MemoryType::DEVICE};  // BLOB_AHWB is not supported on the host.
911 #endif  // __ANDROID__
912         } break;
913         case TestKind::FENCED_COMPUTE: {
914             outputTypesList = {OutputType::FULLY_SPECIFIED};
915             measureTimingList = {false, true};
916             executorList = {Executor::FENCED};
917             memoryTypeList = {MemoryType::ASHMEM};
918         } break;
919         case TestKind::QUANTIZATION_COUPLING: {
920             LOG(FATAL) << "Wrong TestKind for EvaluatePreparedModel";
921             return;
922         } break;
923         case TestKind::INTINITE_LOOP_TIMEOUT: {
924             outputTypesList = {OutputType::MISSED_DEADLINE};
925             measureTimingList = {false, true};
926             executorList = {Executor::SYNC, Executor::BURST, Executor::FENCED};
927             memoryTypeList = {MemoryType::ASHMEM};
928         } break;
929     }
930 
931     for (const OutputType outputType : outputTypesList) {
932         for (const bool measureTiming : measureTimingList) {
933             for (const Executor executor : executorList) {
934                 for (const MemoryType memoryType : memoryTypeList) {
935                     for (const bool reusable : reusableList) {
936                         for (const bool useConfig : useConfigList) {
937                             if ((useConfig || executor == Executor::BURST) && reusable) continue;
938                             const TestConfig testConfig(executor, measureTiming, outputType,
939                                                         memoryType, reusable,
940                                                         /*reportSkipping=*/true, useConfig);
941                             SCOPED_TRACE(toString(testConfig));
942                             EvaluatePreparedModel(device, preparedModel, testModel, testConfig);
943                         }
944                     }
945                 }
946             }
947         }
948     }
949 }
950 
EvaluatePreparedCoupledModels(const std::shared_ptr<IDevice> & device,const std::shared_ptr<IPreparedModel> & preparedModel,const TestModel & testModel,const std::shared_ptr<IPreparedModel> & preparedCoupledModel,const TestModel & coupledModel)951 void EvaluatePreparedCoupledModels(const std::shared_ptr<IDevice>& device,
952                                    const std::shared_ptr<IPreparedModel>& preparedModel,
953                                    const TestModel& testModel,
954                                    const std::shared_ptr<IPreparedModel>& preparedCoupledModel,
955                                    const TestModel& coupledModel) {
956     const std::vector<OutputType> outputTypesList = {OutputType::FULLY_SPECIFIED};
957     const std::vector<bool> measureTimingList = {false, true};
958     const std::vector<Executor> executorList = {Executor::SYNC, Executor::BURST, Executor::FENCED};
959 
960     for (const OutputType outputType : outputTypesList) {
961         for (const bool measureTiming : measureTimingList) {
962             for (const Executor executor : executorList) {
963                 const TestConfig testConfig(executor, measureTiming, outputType, MemoryType::ASHMEM,
964                                             /*reusable=*/false, /*reportSkipping=*/false);
965                 bool baseSkipped = false;
966                 EvaluatePreparedModel(device, preparedModel, testModel, testConfig, &baseSkipped);
967                 bool coupledSkipped = false;
968                 EvaluatePreparedModel(device, preparedCoupledModel, coupledModel, testConfig,
969                                       &coupledSkipped);
970                 ASSERT_EQ(baseSkipped, coupledSkipped);
971                 if (baseSkipped) {
972                     LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
973                                  "execute model that it does not support.";
974                     std::cout << "[          ]   Early termination of test because vendor service "
975                                  "cannot "
976                                  "execute model that it does not support."
977                               << std::endl;
978                     GTEST_SKIP();
979                 }
980             }
981         }
982     }
983 }
984 
Execute(const std::shared_ptr<IDevice> & device,const TestModel & testModel,TestKind testKind)985 void Execute(const std::shared_ptr<IDevice>& device, const TestModel& testModel,
986              TestKind testKind) {
987     Model model = createModel(testModel);
988     if (testKind == TestKind::DYNAMIC_SHAPE) {
989         makeOutputDimensionsUnspecified(&model);
990     }
991 
992     std::shared_ptr<IPreparedModel> preparedModel;
993     switch (testKind) {
994         case TestKind::GENERAL:
995         case TestKind::DYNAMIC_SHAPE:
996         case TestKind::MEMORY_DOMAIN:
997         case TestKind::FENCED_COMPUTE:
998         case TestKind::INTINITE_LOOP_TIMEOUT: {
999             createPreparedModel(device, model, &preparedModel);
1000             if (preparedModel == nullptr) return;
1001             EvaluatePreparedModel(device, preparedModel, testModel, testKind);
1002             int32_t deviceVersion;
1003             ASSERT_TRUE(device->getInterfaceVersion(&deviceVersion).isOk());
1004             if (deviceVersion >= kMinAidlLevelForFL8) {
1005                 createPreparedModel(device, model, &preparedModel, /*reportSkipping*/ true,
1006                                     /*useConfig*/ true);
1007                 EvaluatePreparedModel(device, preparedModel, testModel, testKind);
1008             }
1009         } break;
1010         case TestKind::QUANTIZATION_COUPLING: {
1011             ASSERT_TRUE(testModel.hasQuant8CoupledOperands());
1012             createPreparedModel(device, model, &preparedModel,
1013                                 /*reportSkipping*/ false);
1014             TestModel signedQuantizedModel = convertQuant8AsymmOperandsToSigned(testModel);
1015             std::shared_ptr<IPreparedModel> preparedCoupledModel;
1016             createPreparedModel(device, createModel(signedQuantizedModel), &preparedCoupledModel,
1017                                 /*reportSkipping*/ false);
1018             // If we couldn't prepare a model with unsigned quantization, we must
1019             // fail to prepare a model with signed quantization as well.
1020             if (preparedModel == nullptr) {
1021                 ASSERT_EQ(preparedCoupledModel, nullptr);
1022                 // If we failed to prepare both of the models, we can safely skip
1023                 // the test.
1024                 LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
1025                              "prepare model that it does not support.";
1026                 std::cout
1027                         << "[          ]   Early termination of test because vendor service cannot "
1028                            "prepare model that it does not support."
1029                         << std::endl;
1030                 GTEST_SKIP();
1031             }
1032             ASSERT_NE(preparedCoupledModel, nullptr);
1033             EvaluatePreparedCoupledModels(device, preparedModel, testModel, preparedCoupledModel,
1034                                           signedQuantizedModel);
1035         } break;
1036     }
1037 }
1038 
SetUp()1039 void GeneratedTestBase::SetUp() {
1040     testing::TestWithParam<GeneratedTestParam>::SetUp();
1041     ASSERT_NE(kDevice, nullptr);
1042     const bool deviceIsResponsive =
1043             ndk::ScopedAStatus::fromStatus(AIBinder_ping(kDevice->asBinder().get())).isOk();
1044     ASSERT_TRUE(deviceIsResponsive);
1045     //  TODO(b/201260787): We should require old drivers to report the model as
1046     //  unsupported instead of simply skipping the test.
1047     SkipIfDriverOlderThanTestModel();
1048 }
1049 
SkipIfDriverOlderThanTestModel()1050 void GeneratedTestBase::SkipIfDriverOlderThanTestModel() {
1051     int32_t deviceVersion;
1052     ASSERT_TRUE(kDevice->getInterfaceVersion(&deviceVersion).isOk());
1053     const int32_t modelVersion = kTestModel.getAidlVersionInt();
1054     if (deviceVersion < modelVersion) {
1055         GTEST_SKIP() << "Device interface version " << deviceVersion
1056                      << " is older than test model's minimum supported HAL version " << modelVersion
1057                      << ". Skipping test.";
1058     }
1059 }
1060 
getNamedModels(const FilterFn & filter)1061 std::vector<NamedModel> getNamedModels(const FilterFn& filter) {
1062     return TestModelManager::get().getTestModels(filter);
1063 }
1064 
getNamedModels(const FilterNameFn & filter)1065 std::vector<NamedModel> getNamedModels(const FilterNameFn& filter) {
1066     return TestModelManager::get().getTestModels(filter);
1067 }
1068 
printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam> & info)1069 std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info) {
1070     const auto& [namedDevice, namedModel] = info.param;
1071     return gtestCompliantName(getName(namedDevice) + "_" + getName(namedModel));
1072 }
1073 
1074 // Tag for the generated tests
1075 class GeneratedTest : public GeneratedTestBase {};
1076 
1077 // Tag for the dynamic output shape tests
1078 class DynamicOutputShapeTest : public GeneratedTest {};
1079 
1080 // Tag for the memory domain tests
1081 class MemoryDomainTest : public GeneratedTest {};
1082 
1083 // Tag for the fenced compute tests
1084 class FencedComputeTest : public GeneratedTest {};
1085 
1086 // Tag for the dynamic output shape tests
1087 class QuantizationCouplingTest : public GeneratedTest {};
1088 
1089 // Tag for the loop timeout tests
1090 class InfiniteLoopTimeoutTest : public GeneratedTest {};
1091 
TEST_P(GeneratedTest,Test)1092 TEST_P(GeneratedTest, Test) {
1093     Execute(kDevice, kTestModel, TestKind::GENERAL);
1094 }
1095 
TEST_P(DynamicOutputShapeTest,Test)1096 TEST_P(DynamicOutputShapeTest, Test) {
1097     Execute(kDevice, kTestModel, TestKind::DYNAMIC_SHAPE);
1098 }
1099 
TEST_P(MemoryDomainTest,Test)1100 TEST_P(MemoryDomainTest, Test) {
1101     Execute(kDevice, kTestModel, TestKind::MEMORY_DOMAIN);
1102 }
1103 
TEST_P(FencedComputeTest,Test)1104 TEST_P(FencedComputeTest, Test) {
1105     Execute(kDevice, kTestModel, TestKind::FENCED_COMPUTE);
1106 }
1107 
TEST_P(QuantizationCouplingTest,Test)1108 TEST_P(QuantizationCouplingTest, Test) {
1109     Execute(kDevice, kTestModel, TestKind::QUANTIZATION_COUPLING);
1110 }
1111 
TEST_P(InfiniteLoopTimeoutTest,Test)1112 TEST_P(InfiniteLoopTimeoutTest, Test) {
1113     Execute(kDevice, kTestModel, TestKind::INTINITE_LOOP_TIMEOUT);
1114 }
1115 
1116 INSTANTIATE_GENERATED_TEST(GeneratedTest,
__anon4cf4456d0702(const TestModel& testModel) 1117                            [](const TestModel& testModel) { return !testModel.expectFailure; });
1118 
__anon4cf4456d0802(const TestModel& testModel) 1119 INSTANTIATE_GENERATED_TEST(DynamicOutputShapeTest, [](const TestModel& testModel) {
1120     return !testModel.expectFailure && !testModel.hasScalarOutputs();
1121 });
1122 
1123 INSTANTIATE_GENERATED_TEST(MemoryDomainTest,
__anon4cf4456d0902(const TestModel& testModel) 1124                            [](const TestModel& testModel) { return !testModel.expectFailure; });
1125 
1126 INSTANTIATE_GENERATED_TEST(FencedComputeTest,
__anon4cf4456d0a02(const TestModel& testModel) 1127                            [](const TestModel& testModel) { return !testModel.expectFailure; });
1128 
__anon4cf4456d0b02(const TestModel& testModel) 1129 INSTANTIATE_GENERATED_TEST(QuantizationCouplingTest, [](const TestModel& testModel) {
1130     return !testModel.expectFailure && testModel.hasQuant8CoupledOperands() &&
1131            testModel.main.operations.size() == 1;
1132 });
1133 
__anon4cf4456d0c02(const TestModel& testModel) 1134 INSTANTIATE_GENERATED_TEST(InfiniteLoopTimeoutTest, [](const TestModel& testModel) {
1135     return testModel.isInfiniteLoopTimeoutTest();
1136 });
1137 
1138 }  // namespace aidl::android::hardware::neuralnetworks::vts::functional
1139