]>
Commit | Line | Data |
---|---|---|
fa7225c8 | 1 | /* |
866f8763 | 2 | * Copyright (c) 2016-2017 Apple Inc. All Rights Reserved. |
fa7225c8 A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | ||
25 | /* INSTRUCTIONS FOR ADDING NEW SUBTESTS: | |
26 | * 1. Add the certificates, as DER-encoded files with the 'cer' extension, to OSX/shared_regressions/si-20-sectrust-policies-data/ | |
866f8763 A |
27 | * NOTE: If your cert needs to be named with "(i[Pp]hone|i[Pp]ad|i[Pp]od)", you need to make two copies -- one named properly |
28 | * and another named such that it doesn't match that regex. Use the regex trick below for TARGET_OS_TV to make sure your test | |
29 | * works. | |
fa7225c8 A |
30 | * 2. Add a new dictionary to the test plist (OSX/shared_regressions/si-20-sectrust-policies-data/PinningPolicyTrustTest.plist). |
31 | * This dictionary must include: (see constants below) | |
32 | * MajorTestName | |
33 | * MinorTestName | |
34 | * Policies | |
35 | * Leaf | |
36 | * Intermediates | |
37 | * ExpectedResult | |
38 | * It is strongly recommended that all test dictionaries include the Anchors and VerifyDate keys. | |
39 | * Addtional optional keys are defined below. | |
40 | */ | |
41 | ||
42 | /* INSTRUCTIONS FOR DEBUGGING SUBTESTS: | |
43 | * Add a debugging.plist to OSX/shared_regressions/si-20-sectrust-policies-data/ containing only those subtest dictionaries | |
866f8763 | 44 | * you want to debug. |
fa7225c8 A |
45 | */ |
46 | ||
47 | #include "shared_regressions.h" | |
48 | ||
49 | #include <AssertMacros.h> | |
50 | #import <Foundation/Foundation.h> | |
51 | ||
52 | #include <utilities/SecInternalReleasePriv.h> | |
53 | #include <utilities/SecCFRelease.h> | |
54 | #include <Security/SecCertificate.h> | |
55 | #include <Security/SecCertificatePriv.h> | |
56 | #include <Security/SecPolicyPriv.h> | |
57 | #include <Security/SecTrust.h> | |
58 | ||
59 | /* Key Constants for Test Dictionaries */ | |
60 | const NSString *kSecTrustTestMajorTestName = @"MajorTestName"; /* Required; value: string */ | |
61 | const NSString *kSecTrustTestMinorTestName = @"MinorTestName"; /* Required; value: string */ | |
62 | const NSString *kSecTrustTestPolicies = @"Policies"; /* Required; value: dictionary or array of dictionaries */ | |
63 | const NSString *kSecTrustTestLeaf = @"Leaf"; /* Required; value: string */ | |
64 | const NSString *kSecTrustTestIntermediates = @"Intermediates"; /* Required; value: string or array of strings */ | |
65 | const NSString *kSecTrustTestAnchors = @"Anchors"; /* Recommended; value: string or array of strings */ | |
66 | const NSString *kSecTrustTestVerifyDate = @"VerifyDate"; /* Recommended; value: date */ | |
67 | const NSString *kSecTrustTestExpectedResult = @"ExpectedResult"; /* Required; value: number */ | |
68 | const NSString *kSecTrustTestChainLength = @"ChainLength"; /* Optional; value: number */ | |
866f8763 | 69 | const NSString *kSecTrustTestEnableTestCerts= @"EnableTestCertificates"; /* Optional; value: string */ |
fa7225c8 A |
70 | |
71 | /* Key Constants for Policies Dictionaries */ | |
72 | const NSString *kSecTrustTestPolicyOID = @"PolicyIdentifier"; /* Required; value: string */ | |
73 | const NSString *kSecTrustTestPolicyProperties = @"Properties"; /* Optional; value: dictionary, see Policy Value Constants, SecPolicy.h */ | |
74 | ||
75 | const NSString *kSecTrustTestPinningPolicyResources = @"si-20-sectrust-policies-data"; | |
76 | ||
77 | @interface TestObject : NSObject | |
78 | @property (readonly) NSMutableArray *certificates; | |
79 | @property (readonly) NSMutableArray *policies; | |
80 | @property (readonly) NSMutableArray *anchors; | |
81 | @property (readonly) NSString *fullTestName; | |
82 | ||
83 | - (id)initWithMajorTestName:(NSString *)majorTestName minorTestName:(NSString *)minorTestName; | |
84 | - (bool)addLeafToCertificates:(NSString *)leafName; | |
85 | - (bool)addCertsToArray:(id)pathsObj outputArray:(NSMutableArray *)outArray; | |
86 | - (bool)addIntermediatesToCertificates:(id)intermediatesObj; | |
87 | - (bool)addPolicies:(id)policiesObj; | |
88 | - (bool)addAnchors:(id)anchorsObj; | |
89 | @end | |
90 | ||
91 | @implementation TestObject | |
866f8763 A |
92 | #if TARGET_OS_TV |
93 | /* Mastering removes all files named i[Pp]hone, so dynamically replace any i[Pp]hone's with | |
94 | * iPh0ne. We have two copies in the resources directory. */ | |
95 | - (NSString *)replaceiPhoneNamedFiles:(NSString *)filename { | |
96 | NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:@"iphone" | |
97 | options:NSRegularExpressionCaseInsensitive | |
98 | error:nil]; | |
99 | NSString *newfilename = [regularExpression stringByReplacingMatchesInString:filename | |
100 | options:0 | |
101 | range:NSMakeRange(0, [filename length]) | |
102 | withTemplate:@"iPh0ne"]; | |
103 | return newfilename; | |
104 | } | |
105 | #endif | |
fa7225c8 A |
106 | |
107 | - (id)init { | |
108 | self = [super init]; | |
109 | return self; | |
110 | } | |
111 | ||
112 | - (id)initWithMajorTestName:(NSString *)majorTestName minorTestName:(NSString *)minorTestName { | |
113 | if ((self = [super init])) { | |
114 | _fullTestName = [[majorTestName stringByAppendingString:@"-"] stringByAppendingString:minorTestName]; | |
115 | } | |
116 | return self; | |
117 | } | |
118 | ||
119 | - (bool)addLeafToCertificates:(NSString *)leafName { | |
120 | SecCertificateRef cert; | |
866f8763 | 121 | NSString *path = nil, *filename = nil; |
fa7225c8 A |
122 | require_action_quiet(leafName, errOut, |
123 | fail("%@: failed to get leaf for test", _fullTestName)); | |
866f8763 A |
124 | #if TARGET_OS_TV |
125 | filename = [self replaceiPhoneNamedFiles:leafName]; | |
126 | #else | |
127 | filename = leafName; | |
128 | #endif | |
fa7225c8 A |
129 | |
130 | path = [[NSBundle mainBundle] | |
866f8763 | 131 | pathForResource:filename |
fa7225c8 A |
132 | ofType:@"cer" |
133 | inDirectory:(NSString *)kSecTrustTestPinningPolicyResources]; | |
866f8763 | 134 | require_action_quiet(path, errOut, fail("%@: failed to get path for leaf %@", _fullTestName, filename)); |
fa7225c8 A |
135 | cert = SecCertificateCreateWithData(NULL, (CFDataRef)[NSData dataWithContentsOfFile:path]); |
136 | require_action_quiet(cert, errOut, | |
137 | fail("%@: failed to create leaf certificate from path %@", | |
138 | _fullTestName, path)); | |
139 | _certificates = [[NSMutableArray alloc] initWithObjects:(__bridge id)cert, nil]; | |
140 | CFReleaseNull(cert); | |
141 | require_action_quiet(_certificates, errOut, | |
142 | fail("%@: failed to initialize certificates array", | |
143 | _fullTestName)); | |
144 | return true; | |
145 | ||
146 | errOut: | |
147 | return false; | |
148 | } | |
149 | ||
150 | - (bool)addCertsToArray:(id)pathsObj outputArray:(NSMutableArray *)outArray { | |
151 | __block SecCertificateRef cert = NULL; | |
866f8763 | 152 | __block NSString* path = nil, *filename = nil; |
fa7225c8 A |
153 | require_action_quiet(pathsObj, errOut, |
154 | fail("%@: failed to get certificate paths for test", _fullTestName)); | |
155 | ||
156 | if ([pathsObj isKindOfClass:[NSString class]]) { | |
157 | /* Only one cert path */ | |
866f8763 A |
158 | #if TARGET_OS_TV |
159 | filename = [self replaceiPhoneNamedFiles:pathsObj]; | |
160 | #else | |
161 | filename = pathsObj; | |
162 | #endif | |
fa7225c8 | 163 | path = [[NSBundle mainBundle] |
866f8763 | 164 | pathForResource:filename |
fa7225c8 A |
165 | ofType:@"cer" |
166 | inDirectory:(NSString *)kSecTrustTestPinningPolicyResources]; | |
866f8763 A |
167 | require_action_quiet(path, errOut, fail("%@: failed to get path for cert %@", |
168 | _fullTestName, filename)); | |
fa7225c8 A |
169 | cert = SecCertificateCreateWithData(NULL, (CFDataRef)[NSData dataWithContentsOfFile:path]); |
170 | require_action_quiet(cert, errOut, | |
171 | fail("%@: failed to create certificate from path %@", | |
172 | _fullTestName, path)); | |
173 | [outArray addObject:(__bridge id)cert]; | |
174 | CFReleaseNull(cert); | |
175 | } | |
176 | ||
177 | else if ([pathsObj isKindOfClass:[NSArray class]]) { | |
178 | /* Test has more than one intermediate */ | |
179 | [(NSArray *)pathsObj enumerateObjectsUsingBlock:^(NSString *resource, NSUInteger idx, BOOL *stop) { | |
866f8763 A |
180 | #if TARGET_OS_TV |
181 | filename = [self replaceiPhoneNamedFiles:resource]; | |
182 | #else | |
183 | filename = resource; | |
184 | #endif | |
fa7225c8 | 185 | path = [[NSBundle mainBundle] |
866f8763 | 186 | pathForResource:filename |
fa7225c8 A |
187 | ofType:@"cer" |
188 | inDirectory:(NSString *)kSecTrustTestPinningPolicyResources]; | |
189 | require_action_quiet(path, blockOut, | |
866f8763 A |
190 | fail("%@: failed to get path for cert %ld, %@", |
191 | self->_fullTestName, (unsigned long)idx, filename)); | |
fa7225c8 A |
192 | cert = SecCertificateCreateWithData(NULL, (CFDataRef)[NSData dataWithContentsOfFile:path]); |
193 | require_action_quiet(cert, blockOut, | |
194 | fail("%@: failed to create certificate %ld from path %@", | |
6b200bc3 | 195 | self->_fullTestName, (unsigned long) idx, path)); |
fa7225c8 A |
196 | [outArray addObject:(__bridge id)cert]; |
197 | ||
198 | CFReleaseNull(cert); | |
199 | return; | |
200 | ||
201 | blockOut: | |
202 | CFReleaseNull(cert); | |
203 | *stop = YES; | |
204 | }]; | |
205 | } | |
206 | ||
207 | else { | |
208 | fail("%@: unexpected type for intermediates or anchors value", _fullTestName); | |
209 | goto errOut; | |
210 | } | |
211 | ||
212 | return true; | |
213 | ||
214 | errOut: | |
215 | CFReleaseNull(cert); | |
216 | return false; | |
217 | ||
218 | } | |
219 | ||
220 | - (bool)addIntermediatesToCertificates:(id)intermediatesObj { | |
221 | require_action_quiet(intermediatesObj, errOut, | |
222 | fail("%@: failed to get intermediates for test", _fullTestName)); | |
223 | ||
224 | require_action_quiet([self addCertsToArray:intermediatesObj outputArray:_certificates], errOut, | |
225 | fail("%@: failed to add intermediates to certificates array", _fullTestName)); | |
226 | ||
227 | if ([intermediatesObj isKindOfClass:[NSString class]]) { | |
228 | require_action_quiet([_certificates count] == 2, errOut, | |
229 | fail("%@: failed to add all intermediates", _fullTestName)); | |
230 | } else if ([intermediatesObj isKindOfClass:[NSArray class]]) { | |
231 | require_action_quiet([_certificates count] == [(NSArray *)intermediatesObj count] + 1, errOut, | |
232 | fail("%@: failed to add all intermediates", _fullTestName)); | |
233 | } | |
234 | ||
235 | return true; | |
236 | ||
237 | errOut: | |
238 | return false; | |
239 | } | |
240 | ||
241 | - (bool)addPolicies:(id)policiesObj { | |
242 | __block SecPolicyRef policy = NULL; | |
243 | require_action_quiet(policiesObj, errOut, | |
244 | fail("%@: failed to get policies for test", _fullTestName)); | |
245 | ||
246 | _policies = [[NSMutableArray alloc] init]; | |
247 | require_action_quiet(_policies, errOut, | |
248 | fail("%@: failed to initialize policies array", _fullTestName)); | |
249 | if ([policiesObj isKindOfClass:[NSDictionary class]]) { | |
250 | /* Test has only one policy */ | |
251 | NSString *policyIdentifier = [(NSDictionary *)policiesObj objectForKey:kSecTrustTestPolicyOID]; | |
252 | NSDictionary *policyProperties = [(NSDictionary *)policiesObj objectForKey:kSecTrustTestPolicyProperties]; | |
253 | require_action_quiet(policyIdentifier, errOut, fail("%@: failed to get policy OID", _fullTestName)); | |
254 | ||
255 | policy = SecPolicyCreateWithProperties((__bridge CFStringRef)policyIdentifier, | |
256 | (__bridge CFDictionaryRef)policyProperties); | |
257 | require_action_quiet(policy, errOut, | |
258 | fail("%@: failed to create properties for policy OID %@", | |
259 | _fullTestName, policyIdentifier)); | |
260 | [_policies addObject:(__bridge id)policy]; | |
261 | CFReleaseNull(policy); | |
262 | } | |
263 | ||
264 | else if ([policiesObj isKindOfClass:[NSArray class]]) { | |
265 | /* Test more than one intermediate */ | |
266 | [(NSArray *)policiesObj enumerateObjectsUsingBlock:^(NSDictionary *policyDict, NSUInteger idx, BOOL *stop) { | |
267 | NSString *policyIdentifier = [(NSDictionary *)policyDict objectForKey:kSecTrustTestPolicyOID]; | |
268 | NSDictionary *policyProperties = [(NSDictionary *)policyDict objectForKey:kSecTrustTestPolicyProperties]; | |
6b200bc3 | 269 | require_action_quiet(policyIdentifier, blockOut, fail("%@: failed to get policy OID", self->_fullTestName)); |
fa7225c8 A |
270 | |
271 | policy = SecPolicyCreateWithProperties((__bridge CFStringRef)policyIdentifier, | |
272 | (__bridge CFDictionaryRef)policyProperties); | |
273 | require_action_quiet(policy, blockOut, | |
274 | fail("%@: failed to create properties for policy OID %@", | |
6b200bc3 A |
275 | self->_fullTestName, policyIdentifier)); |
276 | [self->_policies addObject:(__bridge id)policy]; | |
fa7225c8 A |
277 | |
278 | CFReleaseNull(policy); | |
279 | return; | |
280 | ||
281 | blockOut: | |
282 | CFReleaseNull(policy); | |
283 | *stop = YES; | |
284 | }]; | |
285 | ||
286 | require_action_quiet([(NSArray *)policiesObj count] == [_policies count], errOut, | |
287 | fail("%@: failed to add all policies", _fullTestName)); | |
288 | } | |
289 | ||
290 | else { | |
291 | fail("%@: unexpected type for %@ value", _fullTestName, kSecTrustTestPolicies); | |
292 | goto errOut; | |
293 | } | |
294 | ||
295 | return true; | |
296 | ||
297 | errOut: | |
298 | CFReleaseNull(policy); | |
299 | return false; | |
300 | } | |
301 | ||
302 | - (bool)addAnchors:(id)anchorsObj { | |
303 | require_action_quiet(anchorsObj, errOut, | |
304 | fail("%@: failed to get anchors for test", _fullTestName)); | |
305 | ||
306 | _anchors = [[NSMutableArray alloc] init]; | |
307 | require_action_quiet(_anchors, errOut, | |
308 | fail("%@: failed to initialize anchors array", _fullTestName)); | |
309 | require_action_quiet([self addCertsToArray:anchorsObj outputArray:_anchors], errOut, | |
310 | fail("%@: failed to add anchors to anchors array", _fullTestName)); | |
311 | ||
312 | if ([anchorsObj isKindOfClass:[NSString class]]) { | |
313 | require_action_quiet([_anchors count] == 1, errOut, | |
314 | fail("%@: failed to add all anchors", _fullTestName)); | |
315 | } else if ([anchorsObj isKindOfClass:[NSArray class]]) { | |
316 | require_action_quiet([_anchors count] == [(NSArray *)anchorsObj count], errOut, | |
317 | fail("%@: failed to add all anchors", _fullTestName)); | |
318 | } | |
319 | ||
320 | return true; | |
321 | ||
322 | errOut: | |
323 | return false; | |
324 | } | |
325 | ||
326 | @end | |
327 | ||
ecaf5866 | 328 | void (^runPolicyTestForObject)(id, NSUInteger, BOOL *) = |
fa7225c8 A |
329 | ^(NSDictionary *testDict, NSUInteger idx, BOOL *stop) { |
330 | NSString *majorTestName = nil, *minorTestName = nil; | |
331 | TestObject *test = nil; | |
332 | SecTrustRef trust = NULL; | |
333 | SecTrustResultType trustResult = kSecTrustResultInvalid; | |
334 | NSDate *verifyDate = nil; | |
335 | NSNumber *expectedResult = nil, *chainLen = nil; | |
336 | ||
866f8763 A |
337 | /* Test certificates work by default on internal builds. We still need this to |
338 | * determine whether to expect failure for production devices. */ | |
fa7225c8 A |
339 | bool enableTestCertificates = (bool)[testDict objectForKey:kSecTrustTestEnableTestCerts]; |
340 | ||
341 | /* Test name, for documentation purposes */ | |
342 | majorTestName = [testDict objectForKey:kSecTrustTestMajorTestName]; | |
343 | minorTestName = [testDict objectForKey:kSecTrustTestMinorTestName]; | |
344 | require_action_quiet(majorTestName && minorTestName, testOut, | |
345 | fail("Failed to create test names for test %lu",(unsigned long)idx)); | |
346 | test = [[TestObject alloc] initWithMajorTestName:majorTestName minorTestName:minorTestName]; | |
347 | require_action_quiet((test), testOut, fail("%@-%@: failed to create test object", majorTestName, minorTestName)); | |
348 | ||
349 | /* Populate the certificates array */ | |
350 | require_quiet([test addLeafToCertificates:[testDict objectForKey:kSecTrustTestLeaf]], testOut); | |
351 | require_quiet([test addIntermediatesToCertificates:[testDict objectForKey:kSecTrustTestIntermediates]], testOut); | |
352 | ||
fa7225c8 A |
353 | /* Create the policies */ |
354 | require_quiet([test addPolicies:[testDict objectForKey:kSecTrustTestPolicies]], testOut); | |
355 | ||
356 | /* Create the trust object */ | |
357 | require_noerr_action_quiet(SecTrustCreateWithCertificates((__bridge CFArrayRef)test.certificates, | |
358 | (__bridge CFArrayRef)test.policies, | |
359 | &trust), | |
360 | testOut, | |
361 | fail("%@: failed to create trust ref", test.fullTestName)); | |
362 | ||
363 | /* Optionally set anchors in trust object */ | |
364 | if ([testDict objectForKey:kSecTrustTestAnchors]) { | |
365 | require_quiet([test addAnchors:[testDict objectForKey:kSecTrustTestAnchors]], testOut); | |
366 | require_noerr_action_quiet(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)test.anchors), | |
367 | testOut, | |
368 | fail("%@: failed to add anchors to trust ref", test.fullTestName)); | |
369 | } | |
370 | ||
371 | /* Set optional date in trust object */ | |
372 | verifyDate = [testDict objectForKey:kSecTrustTestVerifyDate]; | |
373 | if (verifyDate) { | |
374 | require_noerr_action_quiet(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)verifyDate), testOut, | |
375 | fail("%@: failed to set verify date, %@, in trust ref", test.fullTestName, | |
376 | verifyDate)); | |
377 | } | |
378 | ||
379 | /* Evaluate */ | |
380 | require_noerr_action_quiet(SecTrustEvaluate(trust, &trustResult), testOut, | |
381 | fail("%@: failed to evaluate trust", test.fullTestName)); | |
382 | ||
383 | /* Check results */ | |
384 | require_action_quiet(expectedResult = [testDict objectForKey:kSecTrustTestExpectedResult], | |
385 | testOut, fail("%@: failed to get expected result for test", test.fullTestName)); | |
386 | ||
ecaf5866 | 387 | /* If we enabled test certificates on a non-internal device, expect a failure instead of success. */ |
fa7225c8 A |
388 | if (enableTestCertificates && !SecIsInternalRelease() && ([expectedResult unsignedIntValue] == 4)) { |
389 | ok(trustResult == 5, | |
390 | "%@: actual trust result %u did not match expected trust result %u", | |
391 | test.fullTestName, trustResult, 5); | |
392 | } else { | |
393 | ok(trustResult == [expectedResult unsignedIntValue], | |
394 | "%@: actual trust result %u did not match expected trust result %u", | |
395 | test.fullTestName, trustResult, [expectedResult unsignedIntValue]); | |
396 | } | |
397 | require_quiet(trustResult == [expectedResult unsignedIntValue], testOut); | |
398 | ||
399 | require_quiet(chainLen = [testDict objectForKey:kSecTrustTestChainLength], testOut); | |
400 | require_action_quiet(SecTrustGetCertificateCount(trust) == [chainLen longValue], testOut, | |
401 | fail("%@: actual chain length %ld did not match expected chain length %ld", | |
402 | test.fullTestName, SecTrustGetCertificateCount(trust), [chainLen longValue])); | |
403 | ||
404 | testOut: | |
fa7225c8 A |
405 | CFReleaseNull(trust); |
406 | }; | |
407 | ||
408 | static void tests(void) | |
409 | { | |
410 | NSURL *testPlist = nil; | |
411 | NSArray *testsArray = nil; | |
412 | ||
413 | testPlist = [[NSBundle mainBundle] URLForResource:@"debugging" withExtension:@"plist" | |
414 | subdirectory:(NSString *)kSecTrustTestPinningPolicyResources ]; | |
415 | if (!testPlist) { | |
416 | testPlist = [[NSBundle mainBundle] URLForResource:nil withExtension:@"plist" | |
417 | subdirectory:(NSString *)kSecTrustTestPinningPolicyResources ]; | |
418 | } | |
419 | require_action_quiet(testPlist, exit, | |
420 | fail("Failed to get tests plist from %@", kSecTrustTestPinningPolicyResources)); | |
421 | ||
422 | testsArray = [NSArray arrayWithContentsOfURL: testPlist]; | |
423 | require_action_quiet(testsArray, exit, | |
424 | fail("Failed to create array from plist")); | |
425 | ||
426 | plan_tests((int)[testsArray count]); | |
427 | ||
ecaf5866 | 428 | [testsArray enumerateObjectsUsingBlock:runPolicyTestForObject]; |
fa7225c8 A |
429 | |
430 | exit: | |
431 | return; | |
432 | } | |
433 | ||
434 | int si_20_sectrust_policies(int argc, char *const *argv) | |
435 | { | |
436 | ||
437 | @autoreleasepool { | |
438 | tests(); | |
439 | } | |
440 | ||
441 | return 0; | |
442 | } |