Skip to content

Commit 1abecce

Browse files
shoumikhinfacebook-github-bot
authored andcommitted
Refactor the test suite.
Summary: Factor out all the dynamism and resource files lookup to make that be reusable to other tests. Reviewed By: kirklandsign Differential Revision: D63801081 fbshipit-source-id: e0f59e2ded1fc7a4821858349cc9e205d0d6f75a
1 parent 5acd5c9 commit 1abecce

File tree

7 files changed

+382
-157
lines changed

7 files changed

+382
-157
lines changed

extension/apple/Benchmark/Benchmark.xcodeproj/project.pbxproj

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
/* Begin PBXBuildFile section */
1010
03B2D3682C8A515A0046936E /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B2D3672C8A515A0046936E /* App.swift */; };
11-
03B2D37A2C8A515C0046936E /* Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 03B2D3792C8A515C0046936E /* Tests.mm */; };
11+
03B0118E2CAC567900054791 /* DynamicTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B0118C2CAC567900054791 /* DynamicTestCase.m */; };
12+
03B011912CAD114E00054791 /* ResourceTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B011902CAD114E00054791 /* ResourceTestCase.m */; };
13+
03B2D37A2C8A515C0046936E /* GenericTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 03B2D3792C8A515C0046936E /* GenericTests.mm */; };
1214
03C7FA382C8AA3EC00E6E9AE /* Models in Resources */ = {isa = PBXBuildFile; fileRef = 03C7FA322C8AA24200E6E9AE /* Models */; };
1315
03DD00A92C8FE44600FE4619 /* backend_coreml.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03DD00992C8FE44600FE4619 /* backend_coreml.xcframework */; };
1416
03DD00AA2C8FE44600FE4619 /* kernels_custom.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03DD009A2C8FE44600FE4619 /* kernels_custom.xcframework */; };
@@ -24,7 +26,6 @@
2426
03ED6D152C8AAFFF00F2D6EE /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03ED6D142C8AAFFF00F2D6EE /* Metal.framework */; };
2527
03ED6D172C8AB00500F2D6EE /* CoreML.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03ED6D162C8AB00500F2D6EE /* CoreML.framework */; };
2628
03ED6D192C8AB00A00F2D6EE /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03ED6D182C8AB00A00F2D6EE /* Accelerate.framework */; };
27-
8493389C2C9918950071ABAD /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8493389B2C9918950071ABAD /* UIKit.framework */; };
2829
/* End PBXBuildFile section */
2930

3031
/* Begin PBXContainerItemProxy section */
@@ -44,7 +45,11 @@
4445
03B2D3672C8A515A0046936E /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
4546
03B2D36D2C8A515B0046936E /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
4647
03B2D3752C8A515C0046936E /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
47-
03B2D3792C8A515C0046936E /* Tests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Tests.mm; sourceTree = "<group>"; };
48+
03B0118B2CAC567900054791 /* DynamicTestCase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DynamicTestCase.h; sourceTree = "<group>"; };
49+
03B0118C2CAC567900054791 /* DynamicTestCase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DynamicTestCase.m; sourceTree = "<group>"; };
50+
03B0118F2CAD114E00054791 /* ResourceTestCase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ResourceTestCase.h; sourceTree = "<group>"; };
51+
03B011902CAD114E00054791 /* ResourceTestCase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ResourceTestCase.m; sourceTree = "<group>"; };
52+
03B2D3792C8A515C0046936E /* GenericTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = GenericTests.mm; sourceTree = "<group>"; };
4853
03C7FA322C8AA24200E6E9AE /* Models */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Models; sourceTree = SOURCE_ROOT; };
4954
03DD00992C8FE44600FE4619 /* backend_coreml.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = backend_coreml.xcframework; path = Frameworks/backend_coreml.xcframework; sourceTree = "<group>"; };
5055
03DD009A2C8FE44600FE4619 /* kernels_custom.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = kernels_custom.xcframework; path = Frameworks/kernels_custom.xcframework; sourceTree = "<group>"; };
@@ -79,7 +84,6 @@
7984
03ED6D172C8AB00500F2D6EE /* CoreML.framework in Frameworks */,
8085
03ED6D152C8AAFFF00F2D6EE /* Metal.framework in Frameworks */,
8186
03ED6D132C8AAFF700F2D6EE /* MetalPerformanceShaders.framework in Frameworks */,
82-
8493389C2C9918950071ABAD /* UIKit.framework in Frameworks */,
8387
03ED6D112C8AAFF200F2D6EE /* MetalPerformanceShadersGraph.framework in Frameworks */,
8488
03ED6D0F2C8AAFE900F2D6EE /* libsqlite3.0.tbd in Frameworks */,
8589
03DD00A92C8FE44600FE4619 /* backend_coreml.xcframework in Frameworks */,
@@ -103,6 +107,7 @@
103107
03ED6CEB2C8AAF5300F2D6EE /* Frameworks */,
104108
03C7FA322C8AA24200E6E9AE /* Models */,
105109
03B2D3782C8A515C0046936E /* Tests */,
110+
03B0118D2CAC567900054791 /* TestUtils */,
106111
03B2D3652C8A515A0046936E /* Products */,
107112
);
108113
sourceTree = "<group>";
@@ -128,13 +133,24 @@
128133
03B2D3782C8A515C0046936E /* Tests */ = {
129134
isa = PBXGroup;
130135
children = (
131-
03B2D3792C8A515C0046936E /* Tests.mm */,
136+
03B2D3792C8A515C0046936E /* GenericTests.mm */,
132137
03B019502C8A80D30044D558 /* Tests.xcconfig */,
133138
037C96A02C8A570B00B3DF38 /* Tests.xctestplan */,
134139
);
135140
path = Tests;
136141
sourceTree = SOURCE_ROOT;
137142
};
143+
03B0118D2CAC567900054791 /* TestUtils */ = {
144+
isa = PBXGroup;
145+
children = (
146+
03B0118B2CAC567900054791 /* DynamicTestCase.h */,
147+
03B0118C2CAC567900054791 /* DynamicTestCase.m */,
148+
03B0118F2CAD114E00054791 /* ResourceTestCase.h */,
149+
03B011902CAD114E00054791 /* ResourceTestCase.m */,
150+
);
151+
path = TestUtils;
152+
sourceTree = "<group>";
153+
};
138154
03ED6CEB2C8AAF5300F2D6EE /* Frameworks */ = {
139155
isa = PBXGroup;
140156
children = (
@@ -264,7 +280,9 @@
264280
isa = PBXSourcesBuildPhase;
265281
buildActionMask = 2147483647;
266282
files = (
267-
03B2D37A2C8A515C0046936E /* Tests.mm in Sources */,
283+
03B0118E2CAC567900054791 /* DynamicTestCase.m in Sources */,
284+
03B011912CAD114E00054791 /* ResourceTestCase.m in Sources */,
285+
03B2D37A2C8A515C0046936E /* GenericTests.mm in Sources */,
268286
);
269287
runOnlyForDeploymentPostprocessing = 0;
270288
};
@@ -414,6 +432,7 @@
414432
MARKETING_VERSION = 1.0;
415433
PRODUCT_BUNDLE_IDENTIFIER = org.pytorch.executorch.Benchmark;
416434
PRODUCT_NAME = Benchmark;
435+
REGISTER_APP_GROUPS = NO;
417436
SDKROOT = auto;
418437
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
419438
SUPPORTS_MACCATALYST = NO;
@@ -448,6 +467,7 @@
448467
MARKETING_VERSION = 1.0;
449468
PRODUCT_BUNDLE_IDENTIFIER = org.pytorch.executorch.Benchmark;
450469
PRODUCT_NAME = Benchmark;
470+
REGISTER_APP_GROUPS = NO;
451471
SDKROOT = auto;
452472
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
453473
SUPPORTS_MACCATALYST = NO;
@@ -472,6 +492,7 @@
472492
MARKETING_VERSION = 1.0;
473493
PRODUCT_BUNDLE_IDENTIFIER = org.pytorch.executorch.BenchmarkTests;
474494
PRODUCT_NAME = "$(TARGET_NAME)";
495+
REGISTER_APP_GROUPS = NO;
475496
SDKROOT = auto;
476497
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
477498
SUPPORTS_MACCATALYST = NO;
@@ -497,6 +518,7 @@
497518
MARKETING_VERSION = 1.0;
498519
PRODUCT_BUNDLE_IDENTIFIER = org.pytorch.executorch.BenchmarkTests;
499520
PRODUCT_NAME = "$(TARGET_NAME)";
521+
REGISTER_APP_GROUPS = NO;
500522
SDKROOT = auto;
501523
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
502524
SUPPORTS_MACCATALYST = NO;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#import <XCTest/XCTest.h>
10+
11+
/**
12+
* DynamicTestCase is a subclass of XCTestCase that allows dynamic creation of
13+
* test methods. Subclasses should override the `+dynamicTests` method to
14+
* provide a dictionary of test names and corresponding test blocks.
15+
*/
16+
@interface DynamicTestCase : XCTestCase
17+
18+
/**
19+
* Returns a dictionary mapping test names to test blocks.
20+
* Subclasses should override this method to provide dynamic tests.
21+
*/
22+
+ (NSDictionary<NSString *, void (^)(XCTestCase *)> *)dynamicTests;
23+
24+
@end
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#import "DynamicTestCase.h"
10+
11+
#import <objc/runtime.h>
12+
#import <sys/utsname.h>
13+
14+
#if TARGET_OS_IOS
15+
#import <UIKit/UIDevice.h>
16+
#endif
17+
18+
static NSString *deviceInfoString(void) {
19+
static NSString *deviceInfo;
20+
static dispatch_once_t onceToken;
21+
dispatch_once(&onceToken, ^{
22+
struct utsname systemInfo;
23+
uname(&systemInfo);
24+
#if TARGET_OS_IOS
25+
UIDevice *device = UIDevice.currentDevice;
26+
deviceInfo = [NSString stringWithFormat:@"%@_%@_%@",
27+
device.systemName,
28+
device.systemVersion,
29+
@(systemInfo.machine)];
30+
#elif TARGET_OS_MAC
31+
NSOperatingSystemVersion version = NSProcessInfo.processInfo.operatingSystemVersion;
32+
deviceInfo = [NSString stringWithFormat:@"macOS_%ld_%ld_%ld_%@",
33+
(long)version.majorVersion,
34+
(long)version.minorVersion, (long)version.patchVersion, @(systemInfo.machine)];
35+
#endif // TARGET_OS_IOS
36+
deviceInfo = [[deviceInfo
37+
componentsSeparatedByCharactersInSet:[NSCharacterSet
38+
punctuationCharacterSet]]
39+
componentsJoinedByString:@"_"];
40+
});
41+
return deviceInfo;
42+
}
43+
44+
@implementation DynamicTestCase
45+
46+
+ (void)initialize {
47+
if (self != [DynamicTestCase class]) {
48+
NSString *deviceInfo = deviceInfoString();
49+
[[self dynamicTests]
50+
enumerateKeysAndObjectsUsingBlock:^(NSString *testName,
51+
void (^testCase)(XCTestCase *),
52+
BOOL __unused *stop) {
53+
NSString *methodName =
54+
[NSString stringWithFormat:@"test_%@_%@", testName, deviceInfo];
55+
class_addMethod(self,
56+
NSSelectorFromString(methodName),
57+
imp_implementationWithBlock(testCase),
58+
"v@:");
59+
}];
60+
}
61+
}
62+
63+
+ (NSDictionary<NSString *, void (^)(XCTestCase *)> *)dynamicTests {
64+
return @{};
65+
}
66+
67+
@end
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#import "DynamicTestCase.h"
10+
11+
/**
12+
* ResourceTestCase is a subclass of DynamicTestCase that generates tests based
13+
* on resources. Subclasses should override the bundle, directories, predicates,
14+
* and dynamicTestsForResources methods.
15+
*/
16+
@interface ResourceTestCase : DynamicTestCase
17+
18+
/**
19+
* Returns an array of NSBundle objects to search for resources.
20+
* By default, returns the main bundle and the bundle for the class.
21+
*/
22+
+ (NSArray<NSBundle *> *)bundles;
23+
24+
/**
25+
* Returns an array of directory paths (relative to the bundles' resource paths)
26+
* to search. Subclasses should override to specify directories containing
27+
* resources.
28+
*/
29+
+ (NSArray<NSString *> *)directories;
30+
31+
/**
32+
* Returns a dictionary mapping resource keys to predicates.
33+
* Each predicate is a block that takes a filename and returns a BOOL indicating
34+
* a match. Subclasses should override to specify predicates for matching
35+
* resources.
36+
*/
37+
+ (NSDictionary<NSString *, BOOL (^)(NSString *)> *)predicates;
38+
39+
/**
40+
* Returns a dictionary mapping test names to test blocks, given a dictionary of
41+
* resources. Subclasses should override to provide tests for combinations of
42+
* resources.
43+
*
44+
* @param resources A dictionary mapping resource keys to resource file paths.
45+
* @return A dictionary mapping test names to test blocks.
46+
*/
47+
+ (NSDictionary<NSString *, void (^)(XCTestCase *)> *)dynamicTestsForResources:
48+
(NSDictionary<NSString *, NSString *> *)resources;
49+
50+
@end
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#import "ResourceTestCase.h"
10+
11+
static void generateCombinations(
12+
NSDictionary<NSString *, NSArray<NSString *> *> *matchesByResource,
13+
NSArray<NSString *> *keys,
14+
NSMutableDictionary<NSString *, NSString *> *result,
15+
NSUInteger index,
16+
NSMutableSet<NSDictionary<NSString *, NSString *> *> *combinations) {
17+
if (index == keys.count) {
18+
if (result.count == keys.count) {
19+
[combinations addObject:[result copy]];
20+
}
21+
return;
22+
}
23+
NSString *key = keys[index];
24+
NSArray<NSString *> *matches = matchesByResource[key] ?: @[];
25+
if (!matches.count) {
26+
generateCombinations(
27+
matchesByResource, keys, result, index + 1, combinations);
28+
return;
29+
}
30+
for (NSString *match in matches) {
31+
result[key] = match;
32+
generateCombinations(
33+
matchesByResource, keys, result, index + 1, combinations);
34+
[result removeObjectForKey:key];
35+
}
36+
}
37+
38+
@implementation ResourceTestCase
39+
40+
+ (NSArray<NSBundle *> *)bundles {
41+
return @[ [NSBundle mainBundle], [NSBundle bundleForClass:self] ];
42+
}
43+
44+
+ (NSArray<NSString *> *)directories {
45+
return @[];
46+
}
47+
48+
+ (NSDictionary<NSString *, BOOL (^)(NSString *)> *)predicates {
49+
return @{};
50+
}
51+
52+
+ (NSDictionary<NSString *, void (^)(XCTestCase *)> *)dynamicTestsForResources:
53+
(NSDictionary<NSString *, NSString *> *)resources {
54+
return @{};
55+
}
56+
57+
+ (NSDictionary<NSString *, void (^)(XCTestCase *)> *)dynamicTests {
58+
NSMutableDictionary<NSString *, void (^)(XCTestCase *)> *tests =
59+
[NSMutableDictionary new];
60+
NSMutableSet<NSDictionary<NSString *, NSString *> *> *combinations =
61+
[NSMutableSet new];
62+
NSDictionary<NSString *, BOOL (^)(NSString *)> *predicates =
63+
[self predicates];
64+
NSArray<NSString *> *sortedKeys =
65+
[predicates.allKeys sortedArrayUsingSelector:@selector(compare:)];
66+
67+
if (predicates.count == 0)
68+
return @{};
69+
70+
for (NSBundle *bundle in self.bundles) {
71+
for (NSString *directory in self.directories) {
72+
NSMutableDictionary<NSString *, NSMutableArray<NSString *> *>
73+
*matchesByResource = [NSMutableDictionary new];
74+
NSString *dirPath =
75+
[bundle.resourcePath stringByAppendingPathComponent:directory];
76+
NSArray<NSString *> *files =
77+
[NSFileManager.defaultManager contentsOfDirectoryAtPath:dirPath
78+
error:nil]
79+
?: @[];
80+
for (NSString *file in files) {
81+
NSString *fullPath = [dirPath stringByAppendingPathComponent:file];
82+
for (NSString *key in sortedKeys) {
83+
if (predicates[key](file)) {
84+
matchesByResource[key] =
85+
matchesByResource[key] ?: [NSMutableArray new];
86+
[matchesByResource[key] addObject:fullPath];
87+
}
88+
}
89+
}
90+
NSMutableDictionary<NSString *, NSString *> *result =
91+
[NSMutableDictionary new];
92+
generateCombinations(
93+
matchesByResource, sortedKeys, result, 0, combinations);
94+
}
95+
}
96+
for (NSDictionary<NSString *, NSString *> *resources in combinations) {
97+
NSMutableString *resourceString = [NSMutableString new];
98+
NSCharacterSet *punctuationSet = [NSCharacterSet punctuationCharacterSet];
99+
for (NSString *key in sortedKeys) {
100+
NSString *lastComponent = [resources[key] lastPathComponent];
101+
NSString *cleanedComponent =
102+
[[lastComponent componentsSeparatedByCharactersInSet:punctuationSet]
103+
componentsJoinedByString:@"_"];
104+
[resourceString appendFormat:@"_%@", cleanedComponent];
105+
}
106+
NSDictionary<NSString *, void (^)(XCTestCase *)> *resourceTests =
107+
[self dynamicTestsForResources:resources];
108+
[resourceTests
109+
enumerateKeysAndObjectsUsingBlock:^(
110+
NSString *testName, void (^testBlock)(XCTestCase *), BOOL *stop) {
111+
tests[[testName stringByAppendingString:resourceString]] = testBlock;
112+
}];
113+
}
114+
return tests;
115+
}
116+
117+
@end

0 commit comments

Comments
 (0)