1// Copyright 2018 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at: 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15#import "tensorflow/lite/objc/apis/TFLTensorFlowLite.h" 16 17#import <XCTest/XCTest.h> 18 19NS_ASSUME_NONNULL_BEGIN 20 21/** 22 * Regular expression for TensorFlow Lite runtime version string, e.g. "1.14.0", "0.1.2-alpha.1", 23 * "0.3.4-beta2", "1.14.0-rc.3". 24 */ 25static NSString *const kTFLVersionRegex = @"^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$"; 26 27/** Float model resource name. */ 28static NSString *const kAddFloatModelResourceName = @"add"; 29 30/** Quantized model resource name. */ 31static NSString *const kAddQuantizedModelResourceName = @"add_quantized"; 32 33/** Model resource type. */ 34static NSString *const kAddModelResourceType = @"bin"; 35 36/** Size of the first (and only) dimension of the input and output tensor in the Add model. */ 37enum EnumType : NSUInteger {kAddModelTensorFirstDimensionSize = 2U}; 38 39/** Quantization scale of the quantized model. */ 40static const float kAddQuantizedModelScale = 0.003922F; 41 42/** Quantization zero point of the quantized model. */ 43static const int32_t kAddQuantizedModelZeroPoint = 0; 44 45/** Invalid input tensor index. */ 46static const NSUInteger kInvalidInputTensorIndex = 1U; 47 48/** Invalid output tensor index. */ 49static const NSUInteger kInvalidOutputTensorIndex = 1U; 50 51/** Accuracy used in comparing floating numbers. */ 52static const float kTestAccuracy = 1E-5F; 53/** 54 * Unit tests for TFLInterpreter. 55 */ 56@interface TFLInterpreterTests : XCTestCase 57 58/** Absolute path of the Add float model resource. */ 59@property(nonatomic, nullable) NSString *floatModelPath; 60 61/** Default interpreter using the Add model. */ 62@property(nonatomic, nullable) TFLInterpreter *interpreter; 63 64@end 65 66@implementation TFLInterpreterTests 67 68#pragma mark - XCTestCase 69 70- (void)setUp { 71 [super setUp]; 72 73 NSBundle *bundle = [NSBundle bundleForClass:[self class]]; 74 self.floatModelPath = [bundle pathForResource:kAddFloatModelResourceName 75 ofType:kAddModelResourceType]; 76 NSError *error; 77 self.interpreter = [[TFLInterpreter alloc] initWithModelPath:self.floatModelPath error:&error]; 78 XCTAssertNil(error); 79 XCTAssertNotNil(self.interpreter); 80 XCTAssertTrue([self.interpreter allocateTensorsWithError:nil]); 81} 82 83- (void)tearDown { 84 self.floatModelPath = nil; 85 self.interpreter = nil; 86 87 [super tearDown]; 88} 89 90#pragma mark - Tests 91 92- (void)testTFLVersion { 93 NSRange range = [TFLVersion rangeOfString:kTFLVersionRegex options:NSRegularExpressionSearch]; 94 XCTAssertNotEqual(range.location, NSNotFound); 95} 96 97- (void)testSuccessfulFullRunAddFloatModel { 98 // Shape for both input and output tensor. 99 NSArray<NSNumber *> *shape = @[ @(kAddModelTensorFirstDimensionSize) ]; 100 101 // Creates the interpreter options. 102 TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init]; 103 XCTAssertNotNil(options); 104 options.numberOfThreads = 2; 105 106 // Creates the interpreter. 107 NSError *error; 108 TFLInterpreter *customInterpreter = [[TFLInterpreter alloc] initWithModelPath:self.floatModelPath 109 options:options 110 delegates:@[] 111 error:&error]; 112 XCTAssertNil(error); 113 XCTAssertNotNil(customInterpreter); 114 115 // Allocates memory for tensors. 116 XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]); 117 XCTAssertNil(error); 118 119 // Verifies input and output tensor counts. 120 XCTAssertEqual(customInterpreter.inputTensorCount, 1); 121 XCTAssertEqual(customInterpreter.outputTensorCount, 1); 122 123 // Resizes the intput tensor. 124 XCTAssertTrue([customInterpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]); 125 XCTAssertNil(error); 126 127 // Re-allocates memory for tensors. 128 XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]); 129 XCTAssertNil(error); 130 131 // Verifies the input tensor. 132 TFLTensor *inputTensor = [customInterpreter inputTensorAtIndex:0 error:&error]; 133 XCTAssertNotNil(inputTensor); 134 XCTAssertNil(error); 135 XCTAssertTrue([inputTensor.name isEqualToString:@"input"]); 136 XCTAssertEqual(inputTensor.dataType, TFLTensorDataTypeFloat32); 137 NSArray<NSNumber *> *inputTensorShape = [inputTensor shapeWithError:&error]; 138 XCTAssertNil(error); 139 XCTAssertTrue([shape isEqualToArray:inputTensorShape]); 140 141 // Copies the input data. 142 NSMutableData *inputData = [NSMutableData dataWithCapacity:0]; 143 float one = 1.f; 144 float three = 3.f; 145 [inputData appendBytes:&one length:sizeof(float)]; 146 [inputData appendBytes:&three length:sizeof(float)]; 147 XCTAssertTrue([inputTensor copyData:inputData error:&error]); 148 XCTAssertNil(error); 149 150 // Invokes the interpreter. 151 XCTAssertTrue([customInterpreter invokeWithError:&error]); 152 XCTAssertNil(error); 153 154 // Verifies the output tensor. 155 TFLTensor *outputTensor = [customInterpreter outputTensorAtIndex:0 error:&error]; 156 XCTAssertNotNil(outputTensor); 157 XCTAssertNil(error); 158 XCTAssertTrue([outputTensor.name isEqualToString:@"output"]); 159 XCTAssertEqual(outputTensor.dataType, TFLTensorDataTypeFloat32); 160 NSArray<NSNumber *> *outputTensorShape = [outputTensor shapeWithError:&error]; 161 XCTAssertNil(error); 162 XCTAssertTrue([shape isEqualToArray:outputTensorShape]); 163 164 // Tries to query an invalid output tensor index. 165 TFLTensor *invalidOutputTensor = [customInterpreter outputTensorAtIndex:kInvalidOutputTensorIndex 166 error:&error]; 167 XCTAssertNil(invalidOutputTensor); 168 XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex); 169 170 // Gets the output tensor data. 171 error = nil; 172 NSData *outputData = [outputTensor dataWithError:&error]; 173 XCTAssertNotNil(outputData); 174 XCTAssertNil(error); 175 float output[kAddModelTensorFirstDimensionSize]; 176 [outputData getBytes:output length:(sizeof(float) * kAddModelTensorFirstDimensionSize)]; 177 XCTAssertEqualWithAccuracy(output[0], 3.f, kTestAccuracy); 178 XCTAssertEqualWithAccuracy(output[1], 9.f, kTestAccuracy); 179} 180 181- (void)testSuccessfulFullRunQuantizedModel { 182 // Shape for both input and output tensor. 183 NSArray<NSNumber *> *shape = @[ @(kAddModelTensorFirstDimensionSize) ]; 184 185 // Creates the interpreter options. 186 TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init]; 187 XCTAssertNotNil(options); 188 options.numberOfThreads = 2; 189 190 NSBundle *bundle = [NSBundle bundleForClass:[self class]]; 191 NSString *quantizedModelPath = [bundle pathForResource:kAddQuantizedModelResourceName 192 ofType:kAddModelResourceType]; 193 194 // Creates the interpreter. 195 NSError *error; 196 TFLInterpreter *customInterpreter = [[TFLInterpreter alloc] initWithModelPath:quantizedModelPath 197 options:options 198 delegates:@[] 199 error:&error]; 200 XCTAssertNil(error); 201 XCTAssertNotNil(customInterpreter); 202 203 // Allocates memory for tensors. 204 XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]); 205 XCTAssertNil(error); 206 207 // Verifies input and output tensor counts. 208 XCTAssertEqual(customInterpreter.inputTensorCount, 1); 209 XCTAssertEqual(customInterpreter.outputTensorCount, 1); 210 211 // Resizes the intput tensor. 212 XCTAssertTrue([customInterpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]); 213 XCTAssertNil(error); 214 215 // Re-allocates memory for tensors. 216 XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]); 217 XCTAssertNil(error); 218 219 // Verifies the input tensor. 220 TFLTensor *inputTensor = [customInterpreter inputTensorAtIndex:0 error:&error]; 221 XCTAssertNotNil(inputTensor); 222 XCTAssertNil(error); 223 XCTAssertTrue([inputTensor.name isEqualToString:@"input"]); 224 XCTAssertEqual(inputTensor.dataType, TFLTensorDataTypeUInt8); 225 XCTAssertEqualWithAccuracy(inputTensor.quantizationParameters.scale, kAddQuantizedModelScale, 226 kTestAccuracy); 227 XCTAssertEqual(inputTensor.quantizationParameters.zeroPoint, kAddQuantizedModelZeroPoint); 228 NSArray<NSNumber *> *inputTensorShape = [inputTensor shapeWithError:&error]; 229 XCTAssertNil(error); 230 XCTAssertTrue([shape isEqualToArray:inputTensorShape]); 231 232 // Copies the input data. 233 NSMutableData *inputData = [NSMutableData dataWithCapacity:0]; 234 uint8_t one = 1; 235 uint8_t three = 3; 236 [inputData appendBytes:&one length:sizeof(uint8_t)]; 237 [inputData appendBytes:&three length:sizeof(uint8_t)]; 238 XCTAssertTrue([inputTensor copyData:inputData error:&error]); 239 XCTAssertNil(error); 240 241 // Invokes the interpreter. 242 XCTAssertTrue([customInterpreter invokeWithError:&error]); 243 XCTAssertNil(error); 244 245 // Verifies the output tensor. 246 TFLTensor *outputTensor = [customInterpreter outputTensorAtIndex:0 error:&error]; 247 XCTAssertNotNil(outputTensor); 248 XCTAssertNil(error); 249 XCTAssertTrue([outputTensor.name isEqualToString:@"output"]); 250 XCTAssertEqual(outputTensor.dataType, TFLTensorDataTypeUInt8); 251 XCTAssertEqualWithAccuracy(outputTensor.quantizationParameters.scale, kAddQuantizedModelScale, 252 kTestAccuracy); 253 XCTAssertEqual(outputTensor.quantizationParameters.zeroPoint, kAddQuantizedModelZeroPoint); 254 NSArray<NSNumber *> *outputTensorShape = [outputTensor shapeWithError:&error]; 255 XCTAssertNil(error); 256 XCTAssertTrue([shape isEqualToArray:outputTensorShape]); 257 258 // Tries to query an invalid output tensor index. 259 TFLTensor *invalidOutputTensor = [customInterpreter outputTensorAtIndex:kInvalidOutputTensorIndex 260 error:&error]; 261 XCTAssertNil(invalidOutputTensor); 262 XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex); 263 264 // Gets the output tensor data. 265 error = nil; 266 NSData *outputData = [outputTensor dataWithError:&error]; 267 XCTAssertNotNil(outputData); 268 XCTAssertNil(error); 269 uint8_t output[kAddModelTensorFirstDimensionSize]; 270 [outputData getBytes:output length:(sizeof(uint8_t) * kAddModelTensorFirstDimensionSize)]; 271 XCTAssertEqual(output[0], 3); 272 XCTAssertEqual(output[1], 9); 273} 274 275- (void)testInitWithModelPath_invalidPath { 276 // Creates the interpreter. 277 NSError *error; 278 TFLInterpreter *brokenInterpreter = [[TFLInterpreter alloc] initWithModelPath:@"InvalidPath" 279 error:&error]; 280 XCTAssertNil(brokenInterpreter); 281 XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToLoadModel); 282} 283 284- (void)testInvoke_beforeAllocation { 285 NSError *error; 286 TFLInterpreter *interpreterWithoutAllocation = 287 [[TFLInterpreter alloc] initWithModelPath:self.floatModelPath error:&error]; 288 XCTAssertNotNil(interpreterWithoutAllocation); 289 XCTAssertNil(error); 290 291 XCTAssertFalse([interpreterWithoutAllocation invokeWithError:&error]); 292 XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToInvoke); 293} 294 295- (void)testInputTensorAtIndex_invalidIndex { 296 NSError *error; 297 TFLTensor *inputTensor = [self.interpreter inputTensorAtIndex:kInvalidInputTensorIndex 298 error:&error]; 299 XCTAssertNil(inputTensor); 300 XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex); 301} 302 303- (void)testResizeInputTensorAtIndex_invalidIndex { 304 NSArray<NSNumber *> *shape = @[ @(kAddModelTensorFirstDimensionSize) ]; 305 NSError *error; 306 XCTAssertFalse([self.interpreter resizeInputTensorAtIndex:kInvalidInputTensorIndex 307 toShape:shape 308 error:&error]); 309 XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex); 310} 311 312- (void)testResizeInputTensorAtIndex_emptyShape { 313 NSMutableArray<NSNumber *> *emptyShape = [NSMutableArray arrayWithCapacity:0]; 314 NSError *error; 315 XCTAssertFalse([self.interpreter resizeInputTensorAtIndex:0 toShape:emptyShape error:&error]); 316 XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidShape); 317} 318 319- (void)testResizeInputTensorAtIndex_zeroDimensionSize { 320 NSArray<NSNumber *> *shape = @[ @0 ]; 321 NSError *error; 322 XCTAssertFalse([self.interpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]); 323 XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidShape); 324} 325 326- (void)testCopyDataToInputTensorAtIndex_invalidInputDataByteSize { 327 NSMutableData *inputData = [NSMutableData dataWithCapacity:0]; 328 float one = 1.f; 329 float three = 3.f; 330 [inputData appendBytes:&one length:sizeof(float)]; 331 [inputData appendBytes:&three length:(sizeof(float) - 1)]; 332 NSError *error; 333 TFLTensor *inputTensor = [self.interpreter inputTensorAtIndex:0 error:&error]; 334 XCTAssertNotNil(inputTensor); 335 XCTAssertNil(error); 336 XCTAssertFalse([inputTensor copyData:inputData error:&error]); 337 XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidInputByteSize); 338} 339 340- (void)testCopyDataToOutputTensorAtIndex_notAllowed { 341 NSMutableData *data = [NSMutableData dataWithCapacity:0]; 342 float one = 1.f; 343 float three = 3.f; 344 [data appendBytes:&one length:sizeof(float)]; 345 [data appendBytes:&three length:(sizeof(float) - 1)]; 346 NSError *error; 347 TFLTensor *outputTensor = [self.interpreter outputTensorAtIndex:0 error:&error]; 348 XCTAssertNotNil(outputTensor); 349 XCTAssertNil(error); 350 XCTAssertFalse([outputTensor copyData:data error:&error]); 351 XCTAssertEqual(error.code, TFLInterpreterErrorCodeCopyDataToOutputTensorNotAllowed); 352} 353 354- (void)testNilCDelegate { 355 // Creates the interpreter options. 356 TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init]; 357 358 // Creates the interpreter. 359 NSError *error; 360 TFLDelegate *delegate = [[TFLDelegate alloc] init]; // Base delegate's cDelegate is nil. 361 TFLInterpreter *customInterpreter = [[TFLInterpreter alloc] initWithModelPath:self.floatModelPath 362 options:options 363 delegates:@[ delegate ] 364 error:&error]; 365 XCTAssertNil(error); 366 XCTAssertNotNil(customInterpreter); 367 368 // Allocates memory for tensors. 369 XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]); 370 XCTAssertNil(error); 371} 372 373@end 374 375NS_ASSUME_NONNULL_END 376