Files
wehub-resource-sync 8a852e4b4e
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:14:16 +08:00

377 lines
15 KiB
Objective-C

// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "tensorflow/lite/objc/apis/TFLTensorFlowLite.h"
#import <XCTest/XCTest.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Regular expression for TensorFlow Lite runtime version string, e.g. "1.14.0", "0.1.2-alpha.1",
* "0.3.4-beta2", "1.14.0-rc.3", "2.20.0-dev0+selfbuilt".
*/
static NSString *const kTFLVersionRegex = @"^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?(\\+\\w+)?$";
/** Float model resource name. */
static NSString *const kAddFloatModelResourceName = @"add";
/** Quantized model resource name. */
static NSString *const kAddQuantizedModelResourceName = @"add_quantized";
/** Model resource type. */
static NSString *const kAddModelResourceType = @"bin";
/** Size of the first (and only) dimension of the input and output tensor in the Add model. */
enum EnumType : NSUInteger {kAddModelTensorFirstDimensionSize = 2U};
/** Quantization scale of the quantized model. */
static const float kAddQuantizedModelScale = 0.003922F;
/** Quantization zero point of the quantized model. */
static const int32_t kAddQuantizedModelZeroPoint = 0;
/** Invalid input tensor index. */
static const NSUInteger kInvalidInputTensorIndex = 1U;
/** Invalid output tensor index. */
static const NSUInteger kInvalidOutputTensorIndex = 1U;
/** Accuracy used in comparing floating numbers. */
static const float kTestAccuracy = 1E-5F;
/**
* Unit tests for TFLInterpreter.
*/
@interface TFLInterpreterTests : XCTestCase
/** Absolute path of the Add float model resource. */
@property(nonatomic, nullable) NSString *floatModelPath;
/** Default interpreter using the Add model. */
@property(nonatomic, nullable) TFLInterpreter *interpreter;
@end
@implementation TFLInterpreterTests
#pragma mark - XCTestCase
- (void)setUp {
[super setUp];
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
self.floatModelPath = [bundle pathForResource:kAddFloatModelResourceName
ofType:kAddModelResourceType];
NSError *error;
self.interpreter = [[TFLInterpreter alloc] initWithModelPath:self.floatModelPath error:&error];
XCTAssertNil(error);
XCTAssertNotNil(self.interpreter);
XCTAssertTrue([self.interpreter allocateTensorsWithError:nil]);
}
- (void)tearDown {
self.floatModelPath = nil;
self.interpreter = nil;
[super tearDown];
}
#pragma mark - Tests
- (void)testTFLVersion {
NSLog(@"TFLVersion: %@", TFLVersion);
NSRange range = [TFLVersion rangeOfString:kTFLVersionRegex options:NSRegularExpressionSearch];
XCTAssertNotEqual(range.location, NSNotFound);
}
- (void)testSuccessfulFullRunAddFloatModel {
// Shape for both input and output tensor.
NSArray<NSNumber *> *shape = @[ @(kAddModelTensorFirstDimensionSize) ];
// Creates the interpreter options.
TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init];
XCTAssertNotNil(options);
options.numberOfThreads = 2;
// Creates the interpreter.
NSError *error;
TFLInterpreter *customInterpreter = [[TFLInterpreter alloc] initWithModelPath:self.floatModelPath
options:options
delegates:@[]
error:&error];
XCTAssertNil(error);
XCTAssertNotNil(customInterpreter);
// Allocates memory for tensors.
XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]);
XCTAssertNil(error);
// Verifies input and output tensor counts.
XCTAssertEqual(customInterpreter.inputTensorCount, 1);
XCTAssertEqual(customInterpreter.outputTensorCount, 1);
// Resizes the intput tensor.
XCTAssertTrue([customInterpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]);
XCTAssertNil(error);
// Re-allocates memory for tensors.
XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]);
XCTAssertNil(error);
// Verifies the input tensor.
TFLTensor *inputTensor = [customInterpreter inputTensorAtIndex:0 error:&error];
XCTAssertNotNil(inputTensor);
XCTAssertNil(error);
XCTAssertTrue([inputTensor.name isEqualToString:@"input"]);
XCTAssertEqual(inputTensor.dataType, TFLTensorDataTypeFloat32);
NSArray<NSNumber *> *inputTensorShape = [inputTensor shapeWithError:&error];
XCTAssertNil(error);
XCTAssertTrue([shape isEqualToArray:inputTensorShape]);
// Copies the input data.
NSMutableData *inputData = [NSMutableData dataWithCapacity:0];
float one = 1.f;
float three = 3.f;
[inputData appendBytes:&one length:sizeof(float)];
[inputData appendBytes:&three length:sizeof(float)];
XCTAssertTrue([inputTensor copyData:inputData error:&error]);
XCTAssertNil(error);
// Invokes the interpreter.
XCTAssertTrue([customInterpreter invokeWithError:&error]);
XCTAssertNil(error);
// Verifies the output tensor.
TFLTensor *outputTensor = [customInterpreter outputTensorAtIndex:0 error:&error];
XCTAssertNotNil(outputTensor);
XCTAssertNil(error);
XCTAssertTrue([outputTensor.name isEqualToString:@"output"]);
XCTAssertEqual(outputTensor.dataType, TFLTensorDataTypeFloat32);
NSArray<NSNumber *> *outputTensorShape = [outputTensor shapeWithError:&error];
XCTAssertNil(error);
XCTAssertTrue([shape isEqualToArray:outputTensorShape]);
// Tries to query an invalid output tensor index.
TFLTensor *invalidOutputTensor = [customInterpreter outputTensorAtIndex:kInvalidOutputTensorIndex
error:&error];
XCTAssertNil(invalidOutputTensor);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex);
// Gets the output tensor data.
error = nil;
NSData *outputData = [outputTensor dataWithError:&error];
XCTAssertNotNil(outputData);
XCTAssertNil(error);
float output[kAddModelTensorFirstDimensionSize];
[outputData getBytes:output length:(sizeof(float) * kAddModelTensorFirstDimensionSize)];
XCTAssertEqualWithAccuracy(output[0], 3.f, kTestAccuracy);
XCTAssertEqualWithAccuracy(output[1], 9.f, kTestAccuracy);
}
- (void)testSuccessfulFullRunQuantizedModel {
// Shape for both input and output tensor.
NSArray<NSNumber *> *shape = @[ @(kAddModelTensorFirstDimensionSize) ];
// Creates the interpreter options.
TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init];
XCTAssertNotNil(options);
options.numberOfThreads = 2;
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *quantizedModelPath = [bundle pathForResource:kAddQuantizedModelResourceName
ofType:kAddModelResourceType];
// Creates the interpreter.
NSError *error;
TFLInterpreter *customInterpreter = [[TFLInterpreter alloc] initWithModelPath:quantizedModelPath
options:options
delegates:@[]
error:&error];
XCTAssertNil(error);
XCTAssertNotNil(customInterpreter);
// Allocates memory for tensors.
XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]);
XCTAssertNil(error);
// Verifies input and output tensor counts.
XCTAssertEqual(customInterpreter.inputTensorCount, 1);
XCTAssertEqual(customInterpreter.outputTensorCount, 1);
// Resizes the intput tensor.
XCTAssertTrue([customInterpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]);
XCTAssertNil(error);
// Re-allocates memory for tensors.
XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]);
XCTAssertNil(error);
// Verifies the input tensor.
TFLTensor *inputTensor = [customInterpreter inputTensorAtIndex:0 error:&error];
XCTAssertNotNil(inputTensor);
XCTAssertNil(error);
XCTAssertTrue([inputTensor.name isEqualToString:@"input"]);
XCTAssertEqual(inputTensor.dataType, TFLTensorDataTypeUInt8);
XCTAssertEqualWithAccuracy(inputTensor.quantizationParameters.scale, kAddQuantizedModelScale,
kTestAccuracy);
XCTAssertEqual(inputTensor.quantizationParameters.zeroPoint, kAddQuantizedModelZeroPoint);
NSArray<NSNumber *> *inputTensorShape = [inputTensor shapeWithError:&error];
XCTAssertNil(error);
XCTAssertTrue([shape isEqualToArray:inputTensorShape]);
// Copies the input data.
NSMutableData *inputData = [NSMutableData dataWithCapacity:0];
uint8_t one = 1;
uint8_t three = 3;
[inputData appendBytes:&one length:sizeof(uint8_t)];
[inputData appendBytes:&three length:sizeof(uint8_t)];
XCTAssertTrue([inputTensor copyData:inputData error:&error]);
XCTAssertNil(error);
// Invokes the interpreter.
XCTAssertTrue([customInterpreter invokeWithError:&error]);
XCTAssertNil(error);
// Verifies the output tensor.
TFLTensor *outputTensor = [customInterpreter outputTensorAtIndex:0 error:&error];
XCTAssertNotNil(outputTensor);
XCTAssertNil(error);
XCTAssertTrue([outputTensor.name isEqualToString:@"output"]);
XCTAssertEqual(outputTensor.dataType, TFLTensorDataTypeUInt8);
XCTAssertEqualWithAccuracy(outputTensor.quantizationParameters.scale, kAddQuantizedModelScale,
kTestAccuracy);
XCTAssertEqual(outputTensor.quantizationParameters.zeroPoint, kAddQuantizedModelZeroPoint);
NSArray<NSNumber *> *outputTensorShape = [outputTensor shapeWithError:&error];
XCTAssertNil(error);
XCTAssertTrue([shape isEqualToArray:outputTensorShape]);
// Tries to query an invalid output tensor index.
TFLTensor *invalidOutputTensor = [customInterpreter outputTensorAtIndex:kInvalidOutputTensorIndex
error:&error];
XCTAssertNil(invalidOutputTensor);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex);
// Gets the output tensor data.
error = nil;
NSData *outputData = [outputTensor dataWithError:&error];
XCTAssertNotNil(outputData);
XCTAssertNil(error);
uint8_t output[kAddModelTensorFirstDimensionSize];
[outputData getBytes:output length:(sizeof(uint8_t) * kAddModelTensorFirstDimensionSize)];
XCTAssertEqual(output[0], 3);
XCTAssertEqual(output[1], 9);
}
- (void)testInitWithModelPath_invalidPath {
// Creates the interpreter.
NSError *error;
TFLInterpreter *brokenInterpreter = [[TFLInterpreter alloc] initWithModelPath:@"InvalidPath"
error:&error];
XCTAssertNil(brokenInterpreter);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToLoadModel);
}
- (void)testInvoke_beforeAllocation {
NSError *error;
TFLInterpreter *interpreterWithoutAllocation =
[[TFLInterpreter alloc] initWithModelPath:self.floatModelPath error:&error];
XCTAssertNotNil(interpreterWithoutAllocation);
XCTAssertNil(error);
XCTAssertFalse([interpreterWithoutAllocation invokeWithError:&error]);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToInvoke);
}
- (void)testInputTensorAtIndex_invalidIndex {
NSError *error;
TFLTensor *inputTensor = [self.interpreter inputTensorAtIndex:kInvalidInputTensorIndex
error:&error];
XCTAssertNil(inputTensor);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex);
}
- (void)testResizeInputTensorAtIndex_invalidIndex {
NSArray<NSNumber *> *shape = @[ @(kAddModelTensorFirstDimensionSize) ];
NSError *error;
XCTAssertFalse([self.interpreter resizeInputTensorAtIndex:kInvalidInputTensorIndex
toShape:shape
error:&error]);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex);
}
- (void)testResizeInputTensorAtIndex_emptyShape {
NSMutableArray<NSNumber *> *emptyShape = [NSMutableArray arrayWithCapacity:0];
NSError *error;
XCTAssertFalse([self.interpreter resizeInputTensorAtIndex:0 toShape:emptyShape error:&error]);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidShape);
}
- (void)testResizeInputTensorAtIndex_zeroDimensionSize {
NSArray<NSNumber *> *shape = @[ @0 ];
NSError *error;
XCTAssertFalse([self.interpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidShape);
}
- (void)testCopyDataToInputTensorAtIndex_invalidInputDataByteSize {
NSMutableData *inputData = [NSMutableData dataWithCapacity:0];
float one = 1.f;
float three = 3.f;
[inputData appendBytes:&one length:sizeof(float)];
[inputData appendBytes:&three length:(sizeof(float) - 1)];
NSError *error;
TFLTensor *inputTensor = [self.interpreter inputTensorAtIndex:0 error:&error];
XCTAssertNotNil(inputTensor);
XCTAssertNil(error);
XCTAssertFalse([inputTensor copyData:inputData error:&error]);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidInputByteSize);
}
- (void)testCopyDataToOutputTensorAtIndex_notAllowed {
NSMutableData *data = [NSMutableData dataWithCapacity:0];
float one = 1.f;
float three = 3.f;
[data appendBytes:&one length:sizeof(float)];
[data appendBytes:&three length:(sizeof(float) - 1)];
NSError *error;
TFLTensor *outputTensor = [self.interpreter outputTensorAtIndex:0 error:&error];
XCTAssertNotNil(outputTensor);
XCTAssertNil(error);
XCTAssertFalse([outputTensor copyData:data error:&error]);
XCTAssertEqual(error.code, TFLInterpreterErrorCodeCopyDataToOutputTensorNotAllowed);
}
- (void)testNilCDelegate {
// Creates the interpreter options.
TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init];
// Creates the interpreter.
NSError *error;
TFLDelegate *delegate = [[TFLDelegate alloc] init]; // Base delegate's cDelegate is nil.
TFLInterpreter *customInterpreter = [[TFLInterpreter alloc] initWithModelPath:self.floatModelPath
options:options
delegates:@[ delegate ]
error:&error];
XCTAssertNil(error);
XCTAssertNotNil(customInterpreter);
// Allocates memory for tensors.
XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]);
XCTAssertNil(error);
}
@end
NS_ASSUME_NONNULL_END