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