]> git.saurik.com Git - apple/security.git/blob - supd/Tests/SFAnalyticsTests.m
Security-58286.60.28.tar.gz
[apple/security.git] / supd / Tests / SFAnalyticsTests.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
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 #import <XCTest/XCTest.h>
25 #import "SFAnalytics.h"
26 #import "SFAnalyticsDefines.h"
27 #import <Prequelite/Prequelite.h>
28 #import <CoreFoundation/CFPriv.h>
29 #import <notify.h>
30
31 @interface UnitTestAnalytics : SFAnalytics
32 + (NSString*)databasePath;
33 + (void)setDatabasePath:(NSString*)path;
34 @end
35
36 // MARK: SFAnalytics subclass for custom DB
37
38 @implementation UnitTestAnalytics
39 static NSString* _utapath;
40
41 + (NSString*)databasePath
42 {
43 return _utapath;
44 }
45
46 + (void)setDatabasePath:(NSString*)path
47 {
48 _utapath = path;
49 }
50
51 @end
52
53 @interface SFAnalyticsTests : XCTestCase
54 @end
55
56 @implementation SFAnalyticsTests
57 {
58 UnitTestAnalytics* _analytics;
59 NSString* _dbpath;
60 PQLConnection* _db;
61 }
62
63 static NSString* _path;
64 static NSInteger _testnum;
65 static NSString* build = NULL;
66 static NSString* product = NULL;
67
68 // MARK: Test helper methods
69
70 - (void)assertNoSuccessEvents
71 {
72 XCTAssertFalse([[_db fetch:@"select * from success_count"] next]);
73 }
74
75 - (void)assertNoHardFailures
76 {
77 XCTAssertFalse([[_db fetch:@"select * from hard_failures"] next]);
78 }
79
80 - (void)assertNoSoftFailures
81 {
82 XCTAssertFalse([[_db fetch:@"select * from soft_failures"] next]);
83 }
84
85 - (void)assertNoAllEvents
86 {
87 XCTAssertFalse([[_db fetch:@"select * from all_events"] next]);
88 }
89
90 - (void)assertNoSamples
91 {
92 XCTAssertFalse([[_db fetch:@"select * from samples"] next]);
93 }
94
95 - (void)assertNoEventsAnywhere
96 {
97 [self assertNoAllEvents];
98 [self assertNoSuccessEvents];
99 [self assertNoHardFailures];
100 [self assertNoSoftFailures];
101 [self assertNoSamples];
102 }
103
104 - (void)recentTimeStamp:(NSNumber*)timestamp
105 {
106 XCTAssert([timestamp isKindOfClass:[NSNumber class]], @"Timestamp is an NSNumber");
107 NSDate* eventTime = [NSDate dateWithTimeIntervalSince1970:[timestamp doubleValue]];
108 XCTAssertLessThanOrEqual([[NSDate date] timeIntervalSinceDate:eventTime], 5, @"Timestamp (%@) is pretty recent", timestamp);
109 }
110
111 - (void)properEventLogged:(PQLResultSet*)result eventType:(NSString*)eventType class:(SFAnalyticsEventClass)class attributes:(NSDictionary*)attrs
112 {
113 [self _properEventLogged:result eventType:eventType class:class];
114
115 NSDictionary* rowdata = [NSPropertyListSerialization propertyListWithData:[result dataAtIndex:2] options:NSPropertyListImmutable format:nil error:nil];
116 for (NSString* key in [attrs allKeys]) {
117 XCTAssert([attrs[key] isEqualToString:rowdata[key]], @"Attribute \"%@\" value \"%@\" matches expected \"%@\"", key, rowdata[key], attrs[key]);
118 }
119 XCTAssertFalse([result next], @"only one row returned");
120 }
121
122 - (void)properEventLogged:(PQLResultSet*)result eventType:(NSString*)eventType class:(SFAnalyticsEventClass)class
123 {
124 [self _properEventLogged:result eventType:eventType class:class];
125 XCTAssertFalse([result next], @"only one row returned");
126 }
127
128 - (void)_properEventLogged:(PQLResultSet*)result eventType:(NSString*)eventType class:(SFAnalyticsEventClass)class
129 {
130 XCTAssert([result next], @"result found after adding an event");
131 NSError* error = nil;
132 [result doubleAtIndex:1];
133 NSDictionary* rowdata = [NSPropertyListSerialization propertyListWithData:[result dataAtIndex:2] options:NSPropertyListImmutable format:nil error:&error];
134 XCTAssertNotNil(rowdata, @"able to deserialize db data, %@", error);
135 [self recentTimeStamp:rowdata[SFAnalyticsEventTime]];
136 XCTAssertTrue([rowdata[SFAnalyticsEventType] isKindOfClass:[NSString class]] && [rowdata[SFAnalyticsEventType] isEqualToString:eventType], @"found eventType \"%@\" in db", eventType);
137 XCTAssertTrue([rowdata[SFAnalyticsEventClassKey] isKindOfClass:[NSNumber class]] && [rowdata[SFAnalyticsEventClassKey] intValue] == class, @"eventClass is %ld", class);
138 XCTAssertTrue([rowdata[@"build"] isEqualToString:build], @"event row includes build");
139 XCTAssertTrue([rowdata[@"product"] isEqualToString:product], @"event row includes product");
140 }
141
142 - (void)checkSuccessCountsForEvent:(NSString*)eventType success:(int)success hard:(int)hard soft:(int)soft
143 {
144 PQLResultSet* result = [_db fetch:@"select * from success_count where event_type = %@", eventType];
145 XCTAssert([result next]);
146 XCTAssertTrue([[result stringAtIndex:0] isEqualToString:eventType], @"event name \"%@\", expected \"%@\"", [result stringAtIndex:0], eventType);
147 XCTAssertEqual([result intAtIndex:1], success, @"correct count of successes: %d / %d", [result intAtIndex:1], success);
148 XCTAssertEqual([result intAtIndex:2], hard, @"correct count of successes: %d / %d", [result intAtIndex:2], hard);
149 XCTAssertEqual([result intAtIndex:3], soft, @"correct count of successes: %d / %d", [result intAtIndex:3], soft);
150 XCTAssertFalse([result next], @"no more than one row returned");
151 }
152
153 - (void)checkSamples:(NSArray*)samples name:(NSString*)samplerName totalSamples:(NSUInteger)total accuracy:(double)accuracy
154 {
155 NSUInteger samplescount = 0, targetcount = 0;
156 NSMutableArray* samplesfound = [NSMutableArray array];
157 PQLResultSet* result = [_db fetch:@"select * from samples"];
158 while ([result next]) {
159 ++samplescount;
160 [self recentTimeStamp:[result numberAtIndex:1]];
161 if ([[result stringAtIndex:2] isEqual:samplerName]) {
162 ++targetcount;
163 [samplesfound addObject:[result numberAtIndex:3]];
164 }
165 }
166
167 XCTAssertEqual([samples count], targetcount);
168 XCTAssertEqual(samplescount, total);
169
170 [samplesfound sortUsingSelector:@selector(compare:)];
171 NSArray* sortedInput = [samples sortedArrayUsingSelector:@selector(compare:)];
172 for (NSUInteger idx = 0; idx < [samples count]; ++idx) {
173 XCTAssertEqualWithAccuracy([samplesfound[idx] doubleValue], [sortedInput[idx] doubleValue], accuracy);
174 }
175 }
176
177 - (void)waitForSamplerWork:(double)interval
178 {
179 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
180 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * interval), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
181 dispatch_semaphore_signal(sema);
182 });
183 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
184 }
185
186 // MARK: Test administration
187
188 + (void)setUp
189 {
190 NSError* error;
191 _path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@/", [[NSUUID UUID] UUIDString]]];
192 [[NSFileManager defaultManager] createDirectoryAtPath:_path
193 withIntermediateDirectories:YES
194 attributes:nil
195 error:&error];
196 // No XCTAssert in class method
197 if (error) {
198 NSLog(@"Could not make directory at %@", _path);
199 }
200 NSDictionary *version = CFBridgingRelease(_CFCopySystemVersionDictionary());
201 if (version) {
202 build = version[(__bridge NSString *)_kCFSystemVersionBuildVersionKey];
203 product = version[(__bridge NSString *)_kCFSystemVersionProductNameKey];
204 } else {
205 NSLog(@"could not get build version/product, tests should fail");
206 }
207 }
208
209 - (void)setUp
210 {
211 [super setUp];
212 self.continueAfterFailure = NO;
213 NSError* error = nil;
214 _dbpath = [_path stringByAppendingFormat:@"/test_%ld.db", (long)++_testnum];
215 NSLog(@"sqlite3 %@", _dbpath);
216 [UnitTestAnalytics setDatabasePath:_dbpath];
217 _analytics = [UnitTestAnalytics logger];
218 _db = [PQLConnection new];
219
220 XCTAssertTrue([_db openAtURL:[NSURL URLWithString:_dbpath] sharedCache:NO error:&error]);
221 XCTAssertNil(error, @"could open db");
222 XCTAssertNotNil(_db);
223 }
224
225 - (void)tearDown
226 {
227 NSError *error = nil;
228 XCTAssertTrue([_db close:&error], @"could close db");
229 XCTAssertNil(error, @"No error from closing db");
230 [_analytics removeState];
231 [super tearDown];
232 }
233
234 + (void)tearDown
235 {
236 [[NSFileManager defaultManager] removeItemAtPath:_path error:nil];
237 }
238
239 // MARK: SFAnalytics Tests
240
241 - (void)testDbIsEmptyAtStartup
242 {
243 [self assertNoEventsAnywhere];
244 }
245
246 - (void)testAddingEventsWithNilName
247 {
248 [_analytics logSuccessForEventNamed:nil];
249 [self assertNoEventsAnywhere];
250
251 [_analytics logHardFailureForEventNamed:nil withAttributes:nil];
252 [self assertNoEventsAnywhere];
253
254 [_analytics logSoftFailureForEventNamed:nil withAttributes:nil];
255 [self assertNoEventsAnywhere];
256
257 [_analytics noteEventNamed:nil];
258 [self assertNoEventsAnywhere];
259 }
260
261 - (void)testLogSuccess
262 {
263 [_analytics logSuccessForEventNamed:@"unittestevent"];
264 [self assertNoHardFailures];
265 [self assertNoSoftFailures];
266
267 PQLResultSet* result = [_db fetch:@"select success_count from success_count"];
268 XCTAssert([result next], @"a row was found after adding an event");
269 XCTAssertEqual([result intAtIndex:0], 1, @"success count is 1 after adding an event");
270 XCTAssertFalse([result next], @"only one row found in success_count after inserting a single event");
271 result = [_db fetch:@"select * from all_events"];
272 [self properEventLogged:result eventType:@"unittestevent" class:SFAnalyticsEventClassSuccess];
273 }
274
275 - (void)testLogRecoverableFailure
276 {
277 [_analytics logSoftFailureForEventNamed:@"unittestevent" withAttributes:nil];
278 [self assertNoHardFailures];
279
280 // First check success_count has logged a soft failure
281 [self checkSuccessCountsForEvent:@"unittestevent" success:0 hard:0 soft:1];
282
283 // then check soft_failures itself
284 PQLResultSet* result = [_db fetch:@"select * from soft_failures"];
285 [self properEventLogged:result eventType:@"unittestevent" class:SFAnalyticsEventClassSoftFailure];
286
287 // finally check all_events
288 result = [_db fetch:@"select * from all_events"];
289 [self properEventLogged:result eventType:@"unittestevent" class:SFAnalyticsEventClassSoftFailure];
290 }
291
292 - (void)testLogRecoverablyFailureWithAttributes
293 {
294 NSDictionary* attrs = @{@"attr1" : @"value1", @"attr2" : @"value2"};
295 [_analytics logSoftFailureForEventNamed:@"unittestevent" withAttributes:attrs];
296 [self assertNoHardFailures];
297
298 [self checkSuccessCountsForEvent:@"unittestevent" success:0 hard:0 soft:1];
299
300 // then check soft_failures itself
301 PQLResultSet* result = [_db fetch:@"select * from soft_failures"];
302 [self properEventLogged:result eventType:@"unittestevent" class:SFAnalyticsEventClassSoftFailure attributes:attrs];
303
304 // finally check all_events
305 result = [_db fetch:@"select * from all_events"];
306 [self properEventLogged:result eventType:@"unittestevent" class:SFAnalyticsEventClassSoftFailure attributes:attrs];
307 }
308
309 - (void)testLogUnrecoverableFailure
310 {
311 [_analytics logHardFailureForEventNamed:@"unittestevent" withAttributes:nil];
312 [self assertNoSoftFailures];
313
314 // First check success_count has logged a hard failure
315 [self checkSuccessCountsForEvent:@"unittestevent" success:0 hard:1 soft:0];
316
317 // then check hard_failures itself
318 PQLResultSet* result = [_db fetch:@"select * from hard_failures"];
319 [self properEventLogged:result eventType:@"unittestevent" class:SFAnalyticsEventClassHardFailure];
320
321 // finally check all_events
322 result = [_db fetch:@"select * from all_events"];
323 [self properEventLogged:result eventType:@"unittestevent" class:SFAnalyticsEventClassHardFailure];
324 }
325
326 - (void)testLogUnrecoverableFailureWithAttributes
327 {
328 NSDictionary* attrs = @{@"attr1" : @"value1", @"attr2" : @"value2"};
329 [_analytics logHardFailureForEventNamed:@"unittestevent" withAttributes:attrs];
330 [self assertNoSoftFailures];
331
332 // First check success_count has logged a hard failure
333 [self checkSuccessCountsForEvent:@"unittestevent" success:0 hard:1 soft:0];
334
335 // then check hard_failures itself
336 PQLResultSet* result = [_db fetch:@"select * from hard_failures"];
337 [self properEventLogged:result eventType:@"unittestevent" class:SFAnalyticsEventClassHardFailure attributes:attrs];
338
339 // finally check all_events
340 result = [_db fetch:@"select * from all_events"];
341 [self properEventLogged:result eventType:@"unittestevent" class:SFAnalyticsEventClassHardFailure attributes:attrs];
342 }
343
344 - (void)testLogSeveralEvents
345 {
346 NSDictionary* attrs = @{@"attr1" : @"value1", @"attr2" : @"value2"};
347 int iterations = 100;
348 for (int idx = 0; idx < iterations; ++idx) {
349 [_analytics logHardFailureForEventNamed:@"unittesthardfailure" withAttributes:attrs];
350 [_analytics logSoftFailureForEventNamed:@"unittestsoftfailure" withAttributes:attrs];
351 [_analytics logSuccessForEventNamed:@"unittestsuccess"];
352 [_analytics logHardFailureForEventNamed:@"unittestcombined" withAttributes:attrs];
353 [_analytics logSoftFailureForEventNamed:@"unittestcombined" withAttributes:attrs];
354 [_analytics logSuccessForEventNamed:@"unittestcombined"];
355 }
356
357 [self checkSuccessCountsForEvent:@"unittesthardfailure" success:0 hard:iterations soft:0];
358 [self checkSuccessCountsForEvent:@"unittestsoftfailure" success:0 hard:0 soft:iterations];
359 [self checkSuccessCountsForEvent:@"unittestsuccess" success:iterations hard:0 soft:0];
360 [self checkSuccessCountsForEvent:@"unittestcombined" success:iterations hard:iterations soft:iterations];
361 }
362
363 - (void)testNoteEvent
364 {
365 [_analytics noteEventNamed:@"unittestevent"];
366 [self assertNoSoftFailures];
367 [self assertNoHardFailures];
368
369 // First check success_count has logged a success
370 [self checkSuccessCountsForEvent:@"unittestevent" success:1 hard:0 soft:0];
371
372 PQLResultSet* result = [_db fetch:@"select * from all_events"];
373 [self properEventLogged:result eventType:@"unittestevent" class:SFAnalyticsEventClassNote];
374 }
375
376 // MARK: SFAnalyticsSampler Tests
377
378 - (void)testSamplerSimple
379 {
380 NSString* samplerName = [NSString stringWithFormat:@"UnitTestSamplerSimple_%li", (long)_testnum];
381
382 // This block should be set immediately and fire in 1000ms. Give it a little slack in checking though
383 XCTestExpectation* exp = [self expectationWithDescription:@"waiting for sampler to fire"];
384 [_analytics addMetricSamplerForName:samplerName withTimeInterval:1.0f block:^NSNumber *{
385 [exp fulfill];
386 return @15.3;
387 }];
388 [self waitForExpectations:@[exp] timeout:1.2f];
389 [_analytics removeMetricSamplerForName:samplerName];
390
391 // The expectation is fulfilled before returning and after returning some more work needs to happen. Let it settle down.
392 [self waitForSamplerWork:0.2f];
393
394 [self checkSamples:@[@15.3] name:samplerName totalSamples:1 accuracy:0.01f];
395 }
396
397 // Test state removal mostly
398 - (void)testSamplerSimpleLoop
399 {
400 [self tearDown];
401 for (int idx = 0; idx < 3; ++idx) {
402 [self setUp];
403 @autoreleasepool {
404 [self testSamplerSimple];
405 }
406 [self tearDown];
407 }
408 }
409
410
411 - (void)testSamplerDoesNotFirePrematurely
412 {
413 NSString* samplerName = [NSString stringWithFormat:@"UnitTestSamplerDoesNotFirePrematurely_%li", (long)_testnum];
414 __block BOOL run = NO;
415 [_analytics addMetricSamplerForName:samplerName withTimeInterval:1.0f block:^NSNumber *{
416 run = YES;
417 return @0.9;
418 }];
419
420 [self waitForSamplerWork:0.5f];
421 XCTAssertFalse(run, @"sample did not fire prematurely");
422 [_analytics removeMetricSamplerForName:samplerName];
423 }
424
425 - (void)testSamplerRemove
426 {
427 NSString* samplerName = [NSString stringWithFormat:@"UnitTestSamplerRemove_%li", (long)_testnum];
428 __block BOOL run = NO;
429 [_analytics addMetricSamplerForName:samplerName withTimeInterval:1.0f block:^NSNumber *{
430 run = YES;
431 return @23.8;
432 }];
433 XCTAssertNotNil([_analytics existingMetricSamplerForName:samplerName], @"SFAnalytics held onto the sampler we setup");
434 [_analytics removeMetricSamplerForName:samplerName];
435 XCTAssertNil([_analytics existingMetricSamplerForName:samplerName], @"SFAnalytics got rid of our sampler");
436
437 [self waitForSamplerWork:2.0f];
438 XCTAssertFalse(run, @"sampler did not run after removal");
439 }
440
441 - (void)testSamplerRepeatedSampling
442 {
443 NSString* samplerName = [NSString stringWithFormat:@"UnitTestSamplerRepeatedSampling_%li", (long)_testnum];
444 __block int run = 0;
445 [_analytics addMetricSamplerForName:samplerName withTimeInterval:1.0f block:^NSNumber *{
446 run += 1;
447 return @1.5;
448 }];
449
450 [self waitForSamplerWork:3.5f];
451 [_analytics removeMetricSamplerForName:samplerName];
452 XCTAssertEqual(run, 3, @"sampler ran correct number of times");
453 [self checkSamples:@[@1.5, @1.5, @1.5] name:samplerName totalSamples:3 accuracy:0.01f];
454 }
455
456 - (void)testSamplerDisable
457 {
458 NSString* samplerName = [NSString stringWithFormat:@"UnitTestSamplerDisable_%li", (long)_testnum];
459 __block int run = 0;
460 [_analytics addMetricSamplerForName:samplerName withTimeInterval:1.0f block:^NSNumber *{
461 run += 1;
462 return @44.9;
463 }];
464
465 [[_analytics existingMetricSamplerForName:samplerName] pauseSampling];
466 [self waitForSamplerWork:2.0f];
467 XCTAssertEqual(run, 0, @"sampler did not run while disabled");
468
469 [[_analytics existingMetricSamplerForName:samplerName] resumeSampling];
470 [self waitForSamplerWork:1.3f];
471 XCTAssertEqual(run, 1, @"sampler ran after resuming");
472
473 [self checkSamples:@[@44.9] name:samplerName totalSamples:1 accuracy:0.01f];
474 }
475
476 - (void)testSamplerWithBadData
477 {
478 NSString* samplerName = [NSString stringWithFormat:@"UnitTestSamplerWithBadData_%li", (long)_testnum];
479
480 // bad name
481 XCTAssertNil([_analytics addMetricSamplerForName:nil withTimeInterval:3.0f block:^NSNumber *{
482 return @0.0;
483 }]);
484
485 // bad interval
486 XCTAssertNil([_analytics addMetricSamplerForName:samplerName withTimeInterval:0.0f block:^NSNumber *{
487 return @0.0;
488 }]);
489
490 XCTAssertNil([_analytics addMetricSamplerForName:samplerName withTimeInterval:2.0f block:nil]);
491 }
492
493 - (void)testSamplerOncePerReport
494 {
495 NSString* samplerName = [NSString stringWithFormat:@"UnitTestSamplerOncePerReport_%li", (long)_testnum];
496 __block int run = 0;
497 [_analytics addMetricSamplerForName:samplerName withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport block:^NSNumber *{
498 run += 1;
499 return @74.1;
500 }];
501
502 // There's no point in waiting, it could have been set to some arbitrarily long timer instead
503
504 notify_post(SFAnalyticsFireSamplersNotification);
505 [self waitForSamplerWork:0.5f];
506 XCTAssertEqual(run, 1, @"once-per-report sampler fired once in response to notification");
507 [self checkSamples:@[@74.1] name:samplerName totalSamples:1 accuracy:0.01f];
508 }
509
510 - (void)testSamplerOncePerReportEnsuresSingleSampleInDatabase
511 {
512 NSString* samplerName = [NSString stringWithFormat:@"UnitTestSamplerSetTimeInterval_%li", (long)_testnum];
513 [_analytics addMetricSamplerForName:samplerName withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport block:^NSNumber *{
514 return @57.6;
515 }];
516 notify_post(SFAnalyticsFireSamplersNotification);
517 [self waitForSamplerWork:0.5f];
518 notify_post(SFAnalyticsFireSamplersNotification);
519 [self waitForSamplerWork:0.5f];
520 [self checkSamples:@[@57.6] name:samplerName totalSamples:1 accuracy:0.01f];
521 }
522
523 - (void)testSamplerAddSamplerTwice
524 {
525 NSString* samplerName = [NSString stringWithFormat:@"UnitTestSamplerDisable_%li", (long)_testnum];
526 XCTAssertNotNil([_analytics addMetricSamplerForName:samplerName withTimeInterval:3.0f block:^NSNumber *{
527 return @7.7;
528 }], @"adding first sampler works okay");
529
530 XCTAssertNil([_analytics addMetricSamplerForName:samplerName withTimeInterval:3.0f block:^NSNumber *{
531 return @7.8;
532 }], @"adding duplicate sampler did not work");
533 }
534
535 - (void)testSamplerLogBadSample
536 {
537 [_analytics logMetric:nil withName:@"testsampler"];
538 [self checkSamples:@[] name:@"testsampler" totalSamples:0 accuracy:0.01f];
539
540 id badobj = [NSString stringWithUTF8String:"yolo!"];
541 [_analytics logMetric:badobj withName:@"testSampler"];
542 [self checkSamples:@[] name:@"testsampler" totalSamples:0 accuracy:0.01f];
543 }
544
545 - (void)testSamplerSetTimeInterval
546 {
547 NSString* samplerName = [NSString stringWithFormat:@"UnitTestSamplerSetTimeInterval_%li", (long)_testnum];
548 __block NSUInteger run = 0;
549
550 [_analytics addMetricSamplerForName:samplerName withTimeInterval:1.0f block:^NSNumber *{
551 ++run;
552 return @23.8;
553 }];
554 [self waitForSamplerWork:1.2f];
555 [_analytics existingMetricSamplerForName:samplerName].samplingInterval = 1.5f;
556 [self waitForSamplerWork:2.5f];
557 XCTAssertEqual(run, 2ul);
558 [self checkSamples:@[@23.8, @23.8] name:samplerName totalSamples:2 accuracy:0.01f];
559 }
560
561 // MARK: SFAnalyticsMultiSampler Tests
562
563 - (void)testMultiSamplerSimple
564 {
565 NSString* samplerName = [NSString stringWithFormat:@"UnitTestMultiSamplerSimple_%li", (long)_testnum];
566
567 XCTestExpectation* exp = [self expectationWithDescription:@"waiting for sampler to fire"];
568 [_analytics AddMultiSamplerForName:samplerName withTimeInterval:1.0f block:^NSDictionary<NSString *,NSNumber *> *{
569 [exp fulfill];
570 return @{@"val1" : @89.4f, @"val2" : @11.2f};
571 }];
572 [self waitForExpectations:@[exp] timeout:1.3f];
573 [_analytics removeMultiSamplerForName:samplerName];
574
575 // The expectation is fulfilled before returning and after returning some more work needs to happen. Let it settle down.
576 [self waitForSamplerWork:0.2f];
577
578 [self checkSamples:@[@89.4f] name:@"val1" totalSamples:2 accuracy:0.01f];
579 [self checkSamples:@[@11.2f] name:@"val2" totalSamples:2 accuracy:0.01f];
580 }
581
582 - (void)testMultiSamplerOncePerReport
583 {
584 NSString* samplerName = [NSString stringWithFormat:@"UnitTestMultiSamplerOncePerReport_%li", (long)_testnum];
585 __block int run = 0;
586 [_analytics AddMultiSamplerForName:samplerName withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport block:^NSDictionary<NSString *,NSNumber *> *{
587 run += 1;
588 return @{@"val1" : @33.8f, @"val2" : @54.6f};
589 }];
590
591 // There's no point in waiting, it could have been set to some arbitrarily long timer instead
592
593 notify_post(SFAnalyticsFireSamplersNotification);
594 [self waitForSamplerWork:1.0f];
595 XCTAssertEqual(run, 1, @"once-per-report sampler fired once in response to notification");
596 [self checkSamples:@[@33.8f] name:@"val1" totalSamples:2 accuracy:0.01f];
597 [self checkSamples:@[@54.6f] name:@"val2" totalSamples:2 accuracy:0.01f];
598 }
599
600 - (void)testMultiSamplerSetTimeInterval
601 {
602 NSString* samplerName = [NSString stringWithFormat:@"UnitTestMultiSamplerSetTimeInterval_%li", (long)_testnum];
603 __block NSUInteger run = 0;
604 [_analytics AddMultiSamplerForName:samplerName withTimeInterval:1.0f block:^NSDictionary<NSString *,NSNumber *> *{
605 ++run;
606 return @{@"val1" : @29.3f, @"val2" : @19.3f};
607 }];
608 [self waitForSamplerWork:1.2f];
609 [_analytics existingMultiSamplerForName:samplerName].samplingInterval = 1.5f;
610 [self waitForSamplerWork:2.5f];
611 XCTAssertEqual(run, 2ul);
612 [self checkSamples:@[@29.3f, @29.3f] name:@"val1" totalSamples:4 accuracy:0.01f];
613 [self checkSamples:@[@19.3f, @19.3f] name:@"val2" totalSamples:4 accuracy:0.01f];
614 }
615
616
617 // MARK: SFAnalyticsActivityTracker Tests
618
619 - (void)testTrackerSimple
620 {
621 NSString* trackerName = @"UnitTestTrackerSimple";
622 @autoreleasepool {
623 [_analytics logSystemMetricsForActivityNamed:trackerName withAction:^{
624 [NSThread sleepForTimeInterval:0.3f];
625 }];
626 }
627
628
629 [self checkSamples:@[@(0.3f * NSEC_PER_SEC)] name:trackerName totalSamples:1 accuracy:(0.01f * NSEC_PER_SEC)];
630 }
631
632 - (void)testTrackerMultipleBlocks
633 {
634 NSString* trackerName = @"UnitTestTrackerMultipleBlocks";
635 @autoreleasepool {
636 SFAnalyticsActivityTracker* tracker = [_analytics logSystemMetricsForActivityNamed:trackerName withAction:^{
637 [NSThread sleepForTimeInterval:0.3f];
638 }];
639
640 [tracker performAction:^{
641 [NSThread sleepForTimeInterval:0.2f];
642 }];
643 }
644
645 [self checkSamples:@[@(0.5f * NSEC_PER_SEC)] name:trackerName totalSamples:1 accuracy:(0.1f * NSEC_PER_SEC)];
646 }
647
648 - (void)testTrackerAction
649 {
650 NSString* trackerName = @"UnitTestTrackerOneBlock";
651 @autoreleasepool {
652 SFAnalyticsActivityTracker* tracker = [_analytics logSystemMetricsForActivityNamed:trackerName withAction:NULL];
653 [tracker performAction:^{
654 [NSThread sleepForTimeInterval:0.2f];
655 }];
656 }
657
658 [self checkSamples:@[@(0.2f * NSEC_PER_SEC)] name:trackerName totalSamples:1 accuracy:(0.1f * NSEC_PER_SEC)];
659 }
660
661 - (void)testTrackerStartStop {
662
663 NSString* trackerName = @"UnitTestTrackerStartStop";
664 @autoreleasepool {
665 SFAnalyticsActivityTracker* tracker = [_analytics logSystemMetricsForActivityNamed:trackerName withAction:NULL];
666 [tracker start];
667 [NSThread sleepForTimeInterval:0.2f];
668 [tracker stop];
669 }
670
671 [self checkSamples:@[@(0.2f * NSEC_PER_SEC)] name:trackerName totalSamples:1 accuracy:(0.1f * NSEC_PER_SEC)];
672 }
673
674
675
676 - (void)testTrackerCancel
677 {
678 NSString* trackerName = @"UnitTestTrackerCancel";
679 @autoreleasepool {
680 [[_analytics logSystemMetricsForActivityNamed:trackerName withAction:^{
681 [NSThread sleepForTimeInterval:0.3f];
682 }] cancel];
683 }
684
685 [self assertNoEventsAnywhere];
686 }
687
688
689
690 - (void)testTrackerBadData
691 {
692 // Inspect database to find out it's empty
693 [_analytics logMetric:nil withName:@"fake"];
694 [_analytics logMetric:@3.0 withName:nil];
695
696 // get object back so inspect that, too
697 XCTAssertNil([_analytics logSystemMetricsForActivityNamed:nil withAction:^{return;}]);
698
699 [self assertNoEventsAnywhere];
700 }
701
702 // MARK: Miscellaneous
703
704 - (void)testInstantiateBaseClass
705 {
706 XCTAssertNil([SFAnalytics logger]);
707 }
708
709 - (void)testFuzzyDaysSinceDate
710 {
711 NSInteger secondsPerDay = 60 * 60 * 24;
712 XCTAssertEqual([SFAnalytics fuzzyDaysSinceDate:[NSDate date]], 0);
713 XCTAssertEqual([SFAnalytics fuzzyDaysSinceDate:[NSDate dateWithTimeIntervalSinceNow:secondsPerDay * -3]], 1);
714 XCTAssertEqual([SFAnalytics fuzzyDaysSinceDate:[NSDate dateWithTimeIntervalSinceNow:secondsPerDay * -18]], 7);
715 XCTAssertEqual([SFAnalytics fuzzyDaysSinceDate:[NSDate dateWithTimeIntervalSinceNow:secondsPerDay * -77]], 30);
716 XCTAssertEqual([SFAnalytics fuzzyDaysSinceDate:[NSDate dateWithTimeIntervalSinceNow:secondsPerDay * -370]], 365);
717 XCTAssertEqual([SFAnalytics fuzzyDaysSinceDate:[NSDate distantPast]], 1000);
718 XCTAssertEqual([SFAnalytics fuzzyDaysSinceDate:nil], -1);
719 }
720
721 - (void)testRingBuffer {
722 [self assertNoEventsAnywhere];
723 for (unsigned idx = 0; idx < (SFAnalyticsMaxEventsToReport + 50); ++idx) {
724 [_analytics logHardFailureForEventNamed:@"ringbufferevent" withAttributes:nil];
725 }
726
727 PQLResultSet* result = [_db fetch:@"select count(*) from hard_failures"];
728 XCTAssertTrue([result next], @"Got a count from hard_failures");
729 XCTAssertLessThanOrEqual([result unsignedIntAtIndex:0], SFAnalyticsMaxEventsToReport, @"Ring buffer contains a sane number of events");
730
731 // all_events has a much larger buffer so it should handle the extra events okay
732 result = [_db fetch:@"select count(*) from all_events"];
733 XCTAssertTrue([result next], @"Got a count from all_events");
734 XCTAssertLessThanOrEqual([result unsignedIntAtIndex:0], SFAnalyticsMaxEventsToReport + 50);
735 }
736
737 - (void)testRaceToCreateLoggers
738 {
739 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
740 for (NSInteger idx = 0; idx < 500; ++idx) {
741 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
742 UnitTestAnalytics* logger = [UnitTestAnalytics logger];
743 [logger logSuccessForEventNamed:@"testevent"];
744 dispatch_semaphore_signal(semaphore);
745 });
746 }
747
748 for (NSInteger idx = 0; idx < 500; ++idx) {
749 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
750 }
751 }
752
753 - (void)testDateProperty
754 {
755 NSString* propertyKey = @"testDataPropertyKey";
756 XCTAssertNil([_analytics datePropertyForKey:propertyKey]);
757 NSDate* test = [NSDate date];
758 [_analytics setDateProperty:test forKey:propertyKey];
759 NSDate* retrieved = [_analytics datePropertyForKey:propertyKey];
760 XCTAssert(retrieved);
761 // Storing in SQLite as string loses subsecond resolution, so we need some slack
762 XCTAssertEqualWithAccuracy([test timeIntervalSinceDate:retrieved], 0, 1);
763 [_analytics setDateProperty:nil forKey:propertyKey];
764 XCTAssertNil([_analytics datePropertyForKey:propertyKey]);
765 }
766
767 @end