// // Copyright 2010-2020 Amazon.com, Inc. or its affiliates. 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. // A copy of the License is located at // // http://aws.amazon.com/apache2.0 // // or in the "license" file accompanying this file. This file 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 "AWSPinpointEventRecorderTestBase.h" @interface AWSPinpoint() - (void) destroy; @end @interface AWSPinpointConfiguration() @property (nonatomic, strong) NSUserDefaults *userDefaults; @end @interface AWSPinpointEventRecorderBatchTests : AWSPinpointEventRecorderTestBase @end @implementation AWSPinpointEventRecorderBatchTests - (void) testMultipleEventsWithOneBatchWithSingleSubmitCall { [self validateMultipleEventsWithOneBatchWithSingleSubmitCall:1]; sleep(1); [self validateMultipleEventsWithOneBatchWithSingleSubmitCall:10]; sleep(1); [self validateMultipleEventsWithOneBatchWithSingleSubmitCall:50]; sleep(1); [self validateMultipleEventsWithOneBatchWithSingleSubmitCall:100]; } - (void) validateMultipleEventsWithOneBatchWithSingleSubmitCall:(int) numberOfEvents { __block XCTestExpectation *expectation = [self expectationWithDescription:@"Test finished running."]; AWSPinpointConfiguration *config = [[AWSPinpointConfiguration alloc] initWithAppId:self.appIdIAD launchOptions:nil maxStorageSize:AWSPinpointClientByteLimitDefault sessionTimeout:0]; config.userDefaults = [NSUserDefaults standardUserDefaults]; AWSPinpoint *pinpoint = [AWSPinpointEventRecorderTestBase initializePinpointWithConfig:config]; [pinpoint.analyticsClient.eventRecorder setBatchRecordsByteLimit:DEFAULT_BATCH_LIMIT]; XCTAssertNotNil(pinpoint.analyticsClient.eventRecorder); AWSPinpointEvent *event = [pinpoint.analyticsClient createEventWithEventType:@"TEST_EVENT_MULTIPLE_SUBMIT"]; [event addAttribute:@"Attr1" forKey:@"Attr1"]; [event addMetric:@(1) forKey:@"Mettr1"]; [[pinpoint.analyticsClient.eventRecorder removeAllEvents] waitUntilFinished]; [[[pinpoint.analyticsClient.eventRecorder getEventsWithLimit:@1000] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNotNil(task.result); //Should contain no events after removal XCTAssertEqual([task.result count], 0); return nil; }] waitUntilFinished]; for (int i = 0; i < numberOfEvents; i++) { [[[pinpoint.analyticsClient.eventRecorder saveEvent:event] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); return nil; }] waitUntilFinished]; } [[[pinpoint.analyticsClient.eventRecorder getEventsWithLimit:@1000] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); XCTAssertNotNil(task.result); XCTAssertEqual([task.result count], numberOfEvents); //Extract Event and compare event type and timestamp AWSPinpointEvent *resultEvent = [task.result firstObject]; XCTAssertNotNil(resultEvent); XCTAssertTrue([resultEvent.eventType isEqualToString:event.eventType]); XCTAssertEqual(resultEvent.eventTimestamp, event.eventTimestamp); XCTAssertEqual([[resultEvent.allMetrics objectForKey:@"Mettr1"] intValue], @(1).intValue); XCTAssertTrue([[resultEvent.allAttributes objectForKey:@"Attr1"] isEqualToString:@"Attr1"]); return nil; }] waitUntilFinished]; __block int successfulBatchSubmissions = 0; [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); XCTAssertEqual([task.result count], numberOfEvents); successfulBatchSubmissions += 1; AWSDDLogError(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [[pinpoint.analyticsClient.eventRecorder getEventsWithLimit:@1000] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertFalse(pinpoint.analyticsClient.eventRecorder.submissionInProgress); XCTAssertNil(task.error); XCTAssertNotNil(task.result); //Should contain no events after successful submission XCTAssertEqual([task.result count], 0); [expectation fulfill]; return nil; }]; }); [self waitForExpectationsWithTimeout:8 handler:^(NSError * _Nullable error) { XCTAssertEqual(successfulBatchSubmissions, 1); XCTAssertNil(error); }]; [pinpoint destroy]; } - (void) test1MultipleEventsWithOneBatchWithMultipleSubmitCallsFromSingleThread { [self validateMultipleEventsWithOneBatchWithMultipleSubmitCallsFromSingleThread:1]; } - (void) test10MultipleEventsWithOneBatchWithMultipleSubmitCallsFromSingleThread { [self validateMultipleEventsWithOneBatchWithMultipleSubmitCallsFromSingleThread:10]; } - (void) test50MultipleEventsWithOneBatchWithMultipleSubmitCallsFromSingleThread { [self validateMultipleEventsWithOneBatchWithMultipleSubmitCallsFromSingleThread:50]; } - (void) test100MultipleEventsWithOneBatchWithMultipleSubmitCallsFromSingleThread { [self validateMultipleEventsWithOneBatchWithMultipleSubmitCallsFromSingleThread:100]; } - (void) test250MultipleEventsWithOneBatchWithMultipleSubmitCallsFromSingleThread { [self validateMultipleEventsWithOneBatchWithMultipleSubmitCallsFromSingleThread:250]; } - (void) validateMultipleEventsWithOneBatchWithMultipleSubmitCallsFromSingleThread:(int) numberOfEvents { __block XCTestExpectation *expectation = [self expectationWithDescription:@"Test finished running."]; AWSPinpointConfiguration *config = [[AWSPinpointConfiguration alloc] initWithAppId:self.appIdIAD launchOptions:nil maxStorageSize:AWSPinpointClientByteLimitDefault sessionTimeout:0]; config.userDefaults = [NSUserDefaults standardUserDefaults]; AWSPinpoint *pinpoint = [AWSPinpointEventRecorderTestBase initializePinpointWithConfig:config]; [pinpoint.analyticsClient.eventRecorder setBatchRecordsByteLimit:DEFAULT_BATCH_LIMIT]; XCTAssertNotNil(pinpoint.analyticsClient.eventRecorder); AWSPinpointEvent *event = [pinpoint.analyticsClient createEventWithEventType:@"TEST_EVENT_MULTIPLE_SUBMIT"]; [event addAttribute:@"Attr1" forKey:@"Attr1"]; [event addMetric:@(1) forKey:@"Mettr1"]; [[pinpoint.analyticsClient.eventRecorder removeAllEvents] waitUntilFinished]; [[[pinpoint.analyticsClient.eventRecorder getEventsWithLimit:@1000] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); XCTAssertNotNil(task.result); //Should contain no events after removal XCTAssertEqual([task.result count], 0); return nil; }] waitUntilFinished]; for (int i = 0; i < numberOfEvents; i++) { [[[pinpoint.analyticsClient.eventRecorder saveEvent:event] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); return nil; }] waitUntilFinished]; } [[[pinpoint.analyticsClient.eventRecorder getEventsWithLimit:@1000] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); XCTAssertNotNil(task.result); XCTAssertEqual([task.result count], numberOfEvents); //Extract Event and compare event type and timestamp AWSPinpointEvent *resultEvent = [task.result firstObject]; XCTAssertNotNil(resultEvent); XCTAssertTrue([resultEvent.eventType isEqualToString:event.eventType]); XCTAssertEqual(resultEvent.eventTimestamp, event.eventTimestamp); XCTAssertEqual([[resultEvent.allMetrics objectForKey:@"Mettr1"] intValue], @(1).intValue); XCTAssertTrue([[resultEvent.allAttributes objectForKey:@"Attr1"] isEqualToString:@"Attr1"]); return nil; }] waitUntilFinished]; __block int successfulBatchSubmissions = 0; for (int i = 0; i < numberOfEvents; i++) { [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); XCTAssertEqual([task.result count], numberOfEvents); successfulBatchSubmissions += 1; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [[pinpoint.analyticsClient.eventRecorder getEventsWithLimit:@1000] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertFalse(pinpoint.analyticsClient.eventRecorder.submissionInProgress); XCTAssertNil(task.error); XCTAssertNotNil(task.result); //Should contain no events after successful submission XCTAssertEqual([task.result count], 0); [expectation fulfill]; return nil; }]; }); [self waitForExpectationsWithTimeout:11 handler:^(NSError * _Nullable error) { XCTAssertEqual(successfulBatchSubmissions, 1); XCTAssertNil(error); }]; [pinpoint destroy]; } - (void) test1MultipleEventsWithOneBatchWithMultipleSubmitCallsFromMultipleThreads { [self validateMultipleEventsWithOneBatchWithMultipleSubmitCallsFromMultipleThreads:1]; } - (void) test10MultipleEventsWithOneBatchWithMultipleSubmitCallsFromMultipleThreads { [self validateMultipleEventsWithOneBatchWithMultipleSubmitCallsFromMultipleThreads:10]; } - (void) test50MultipleEventsWithOneBatchWithMultipleSubmitCallsFromMultipleThreads { [self validateMultipleEventsWithOneBatchWithMultipleSubmitCallsFromMultipleThreads:50]; } - (void) test100MultipleEventsWithOneBatchWithMultipleSubmitCallsFromMultipleThreads { [self validateMultipleEventsWithOneBatchWithMultipleSubmitCallsFromMultipleThreads:100]; } - (AWSPinpoint *)createAWSPinpointWithConfig:(AWSPinpointConfiguration *)config batchByteLimit:(int) byteBatchLimit { config.userDefaults = [NSUserDefaults standardUserDefaults]; AWSPinpoint *pinpoint = [AWSPinpointEventRecorderTestBase initializePinpointWithConfig:config]; [pinpoint.analyticsClient.eventRecorder setBatchRecordsByteLimit:byteBatchLimit]; return pinpoint; } - (void) validateMultipleEventsWithOneBatchWithMultipleSubmitCallsFromMultipleThreads:(int) numberOfEvents { __block XCTestExpectation *expectation = [self expectationWithDescription:@"Test finished running."]; AWSPinpointConfiguration *config = [[AWSPinpointConfiguration alloc] initWithAppId:self.appIdIAD launchOptions:nil maxStorageSize:AWSPinpointClientByteLimitDefault sessionTimeout:0]; AWSPinpoint * pinpoint = [self createAWSPinpointWithConfig:config batchByteLimit:DEFAULT_BATCH_LIMIT]; XCTAssertNotNil(pinpoint.analyticsClient.eventRecorder); AWSPinpointEvent *event = [pinpoint.analyticsClient createEventWithEventType:@"TEST_EVENT_MULTIPLE_SUBMIT"]; [event addAttribute:@"Attr1" forKey:@"Attr1"]; [event addMetric:@(1) forKey:@"Mettr1"]; [[pinpoint.analyticsClient.eventRecorder removeAllEvents] waitUntilFinished]; [[[pinpoint.analyticsClient.eventRecorder getEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); XCTAssertNotNil(task.result); //Should contain no events after removal XCTAssertEqual([task.result count], 0); return nil; }] waitUntilFinished]; for (int i = 0; i < numberOfEvents; i++) { [[[pinpoint.analyticsClient.eventRecorder saveEvent:event] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); return nil; }] waitUntilFinished]; } [[[pinpoint.analyticsClient.eventRecorder getEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); XCTAssertNotNil(task.result); XCTAssertEqual([task.result count], numberOfEvents); //Extract Event and compare event type and timestamp AWSPinpointEvent *resultEvent = [task.result firstObject]; XCTAssertNotNil(resultEvent); XCTAssertTrue([resultEvent.eventType isEqualToString:event.eventType]); XCTAssertEqual(resultEvent.eventTimestamp, event.eventTimestamp); XCTAssertEqual([[resultEvent.allMetrics objectForKey:@"Mettr1"] intValue], @(1).intValue); XCTAssertTrue([[resultEvent.allAttributes objectForKey:@"Attr1"] isEqualToString:@"Attr1"]); return nil; }] waitUntilFinished]; __block int successfulBatchSubmissions = 0; [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); XCTAssertEqual([task.result count], numberOfEvents); successfulBatchSubmissions += 1; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); XCTAssertEqual([task.result count], numberOfEvents); successfulBatchSubmissions += 1; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); XCTAssertEqual([task.result count], numberOfEvents); successfulBatchSubmissions += 1; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); XCTAssertEqual([task.result count], numberOfEvents); successfulBatchSubmissions += 1; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); XCTAssertEqual([task.result count], numberOfEvents); successfulBatchSubmissions += 1; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(14 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [[pinpoint.analyticsClient.eventRecorder getEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertFalse(pinpoint.analyticsClient.eventRecorder.submissionInProgress); XCTAssertNil(task.error); XCTAssertNotNil(task.result); //Should contain no events after successful submission XCTAssertEqual([task.result count], 0); [expectation fulfill]; return nil; }]; }); [self waitForExpectationsWithTimeout:15 handler:^(NSError * _Nullable error) { XCTAssertEqual(successfulBatchSubmissions, 1); XCTAssertNil(error); }]; [pinpoint destroy]; } - (void) test1MultipleEventsWithMultipleBatchesWithSingleSubmitCallSingleThread { [self validateMultipleEventsWithMultipleBatchesWithSingleSubmitCall:1]; } - (void) test10MultipleEventsWithMultipleBatchesWithSingleSubmitCallSingleThread { [self validateMultipleEventsWithMultipleBatchesWithSingleSubmitCall:10]; } - (void) test50MultipleEventsWithMultipleBatchesWithSingleSubmitCallSingleThread { [self validateMultipleEventsWithMultipleBatchesWithSingleSubmitCall:50]; } - (void) test100MultipleEventsWithMultipleBatchesWithSingleSubmitCallSingleThread { [self validateMultipleEventsWithMultipleBatchesWithSingleSubmitCall:100]; } - (void) validateMultipleEventsWithMultipleBatchesWithSingleSubmitCall:(int) numberOfEvents { __block XCTestExpectation *expectation = [self expectationWithDescription:@"Test finished running."]; AWSPinpointConfiguration *config = [[AWSPinpointConfiguration alloc] initWithAppId:self.appIdIAD launchOptions:nil maxStorageSize:AWSPinpointClientByteLimitDefault sessionTimeout:0]; config.userDefaults = [NSUserDefaults standardUserDefaults]; AWSPinpoint *pinpoint = [AWSPinpointEventRecorderTestBase initializePinpointWithConfig:config]; [pinpoint.analyticsClient.eventRecorder setBatchRecordsByteLimit:10]; XCTAssertNotNil(pinpoint.analyticsClient.eventRecorder); AWSPinpointEvent *event = [pinpoint.analyticsClient createEventWithEventType:@"TEST_EVENT_MULTIPLE_SUBMIT"]; [event addAttribute:@"Attr1" forKey:@"Attr1"]; [event addMetric:@(1) forKey:@"Mettr1"]; [self removeAllEventsAndVerify:pinpoint]; for (int i = 0; i < numberOfEvents; i++) { [[[pinpoint.analyticsClient.eventRecorder saveEvent:event] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); return nil; }] waitUntilFinished]; } [[[pinpoint.analyticsClient.eventRecorder getEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); XCTAssertNotNil(task.result); XCTAssertEqual([task.result count], numberOfEvents); //Extract Event and compare event type and timestamp AWSPinpointEvent *resultEvent = [task.result firstObject]; XCTAssertNotNil(resultEvent); XCTAssertTrue([resultEvent.eventType isEqualToString:event.eventType]); XCTAssertEqual(resultEvent.eventTimestamp, event.eventTimestamp); XCTAssertEqual([[resultEvent.allMetrics objectForKey:@"Mettr1"] intValue], @(1).intValue); XCTAssertTrue([[resultEvent.allAttributes objectForKey:@"Attr1"] isEqualToString:@"Attr1"]); return nil; }] waitUntilFinished]; __block int successfulBatchSubmissions = 0; [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); XCTAssertEqual([task.result count], numberOfEvents); successfulBatchSubmissions += 1; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; int timeout = numberOfEvents/2; if (timeout < 10) { timeout = 10; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [[pinpoint.analyticsClient.eventRecorder getEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertFalse(pinpoint.analyticsClient.eventRecorder.submissionInProgress); XCTAssertNil(task.error); XCTAssertNotNil(task.result); //Should contain no events after successful submission XCTAssertEqual([task.result count], 0); [expectation fulfill]; return nil; }]; }); [self waitForExpectationsWithTimeout:(timeout + 1) handler:^(NSError * _Nullable error) { XCTAssertEqual(successfulBatchSubmissions, 1); XCTAssertNil(error); }]; [pinpoint destroy]; } -(void) test1MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsSingleThread { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCalls:1]; } -(void) test10MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsSingleThread { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCalls:10]; } -(void) test50MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsSingleThread { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCalls:50]; } -(void) test100MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsSingleThread { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCalls:100]; } - (void) validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCalls:(int) numberOfEvents { __block XCTestExpectation *expectation = [self expectationWithDescription:@"Test finished running."]; AWSPinpointConfiguration *config = [[AWSPinpointConfiguration alloc] initWithAppId:self.appIdIAD launchOptions:nil maxStorageSize:AWSPinpointClientByteLimitDefault sessionTimeout:0]; config.userDefaults = [NSUserDefaults standardUserDefaults]; AWSPinpoint *pinpoint = [AWSPinpointEventRecorderTestBase initializePinpointWithConfig:config]; [pinpoint.analyticsClient.eventRecorder setBatchRecordsByteLimit:10]; //Each batch will contain 1 event XCTAssertNotNil(pinpoint.analyticsClient.eventRecorder); [self removeAllEventsAndVerify:pinpoint]; for (int i = 0; i < numberOfEvents; i++) { AWSPinpointEvent *event = [pinpoint.analyticsClient createEventWithEventType: [NSString stringWithFormat:@"TEST_EVENT_MULTIPLE_SUBMIT %d", i]]; [event addAttribute:@"Attr1" forKey:@"Attr1"]; [event addMetric:@(1) forKey:@"Mettr1"]; [[[pinpoint.analyticsClient.eventRecorder saveEvent:event] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); return nil; }] waitUntilFinished]; } [[[pinpoint.analyticsClient.eventRecorder getEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); XCTAssertNotNil(task.result); XCTAssertEqual([task.result count], numberOfEvents); //Extract Event and compare event type and timestamp AWSPinpointEvent *resultEvent = [task.result firstObject]; XCTAssertNotNil(resultEvent); XCTAssertEqual([[resultEvent.allMetrics objectForKey:@"Mettr1"] intValue], @(1).intValue); XCTAssertTrue([[resultEvent.allAttributes objectForKey:@"Attr1"] isEqualToString:@"Attr1"]); return nil; }] waitUntilFinished]; __block int successfulBatchSubmissions = 0; __block int successfulEventsSubmitted = 0; for (int i=0; i < numberOfEvents; i++) { [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); successfulBatchSubmissions += 1; successfulEventsSubmitted += [task.result count]; NSLog(@"Submitted batch with %lu events inside.", [task.result count]); } else { NSLog(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; } int timeout = numberOfEvents/2; if (timeout < 10) { timeout = 10; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [[pinpoint.analyticsClient.eventRecorder getEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertFalse(pinpoint.analyticsClient.eventRecorder.submissionInProgress); XCTAssertNotNil(task.result); //Should contain no events after successful submission XCTAssertEqual([task.result count], 0); [expectation fulfill]; return nil; }]; }); [self waitForExpectationsWithTimeout:(timeout + 1) handler:^(NSError * _Nullable error) { [[pinpoint.analyticsClient.eventRecorder removeAllEvents] waitUntilFinished]; XCTAssertEqual(successfulEventsSubmitted, numberOfEvents); XCTAssertEqual(successfulBatchSubmissions, 1); XCTAssertNil(error); }]; [pinpoint destroy]; } - (void) test1MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreads { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreads:1]; } - (void) test10MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreads { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreads:10]; } - (void) test50MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreads { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreads:50]; } - (void) test100MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreads { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreads:100]; } - (void) validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreads:(int) numberOfEvents { __block XCTestExpectation *expectation = [self expectationWithDescription:@"Test finished running."]; AWSPinpointConfiguration *config = [[AWSPinpointConfiguration alloc] initWithAppId:self.appIdIAD launchOptions:nil maxStorageSize:AWSPinpointClientByteLimitDefault sessionTimeout:0]; config.userDefaults = [NSUserDefaults standardUserDefaults]; AWSPinpoint *pinpoint = [AWSPinpointEventRecorderTestBase initializePinpointWithConfig:config]; [pinpoint.analyticsClient.eventRecorder setBatchRecordsByteLimit:10]; XCTAssertNotNil(pinpoint.analyticsClient.eventRecorder); AWSPinpointEvent *event = [pinpoint.analyticsClient createEventWithEventType:@"TEST_EVENT_MULTIPLE_SUBMIT"]; [event addAttribute:@"Attr1" forKey:@"Attr1"]; [event addMetric:@(1) forKey:@"Mettr1"]; [self removeAllEventsAndVerify:pinpoint]; for (int i = 0; i < numberOfEvents; i++) { [[[pinpoint.analyticsClient.eventRecorder saveEvent:event] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); return nil; }] waitUntilFinished]; } [[[pinpoint.analyticsClient.eventRecorder getEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); XCTAssertNotNil(task.result); XCTAssertEqual([task.result count], numberOfEvents); //Extract Event and compare event type and timestamp AWSPinpointEvent *resultEvent = [task.result firstObject]; XCTAssertNotNil(resultEvent); XCTAssertTrue([resultEvent.eventType isEqualToString:event.eventType]); XCTAssertEqual(resultEvent.eventTimestamp, event.eventTimestamp); XCTAssertEqual([[resultEvent.allMetrics objectForKey:@"Mettr1"] intValue], @(1).intValue); XCTAssertTrue([[resultEvent.allAttributes objectForKey:@"Attr1"] isEqualToString:@"Attr1"]); return nil; }] waitUntilFinished]; __block int successfulBatchSubmissions = 0; __block int successfulEventsSubmitted = 0; [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); successfulBatchSubmissions += 1; successfulEventsSubmitted += [task.result count]; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); successfulBatchSubmissions += 1; successfulEventsSubmitted += [task.result count]; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); successfulBatchSubmissions += 1; successfulEventsSubmitted += [task.result count]; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); successfulBatchSubmissions += 1; successfulEventsSubmitted += [task.result count]; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); successfulBatchSubmissions += 1; successfulEventsSubmitted += [task.result count]; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; }); int timeout = numberOfEvents/2; if (timeout < 10) { timeout = 10; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [[pinpoint.analyticsClient.eventRecorder getEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertFalse(pinpoint.analyticsClient.eventRecorder.submissionInProgress); XCTAssertNotNil(task.result); //Should contain no events after successful submission XCTAssertEqual([task.result count], 0); [expectation fulfill]; return nil; }]; }); [self waitForExpectationsWithTimeout:(timeout + 1) handler:^(NSError * _Nullable error) { XCTAssertEqual(successfulEventsSubmitted, numberOfEvents); XCTAssertEqual(successfulBatchSubmissions, 1); XCTAssertNil(error); }]; [pinpoint destroy]; } - (void) test1MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreadsThenRecordMoreEventsAndSubmitAgain { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreadsThenRecordMoreEventsAndSubmitAgain:1]; } - (void) test10MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreadsThenRecordMoreEventsAndSubmitAgain { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreadsThenRecordMoreEventsAndSubmitAgain:10]; } - (void) test50MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreadsThenRecordMoreEventsAndSubmitAgain { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreadsThenRecordMoreEventsAndSubmitAgain:50]; } - (void) test100MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreadsThenRecordMoreEventsAndSubmitAgain { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreadsThenRecordMoreEventsAndSubmitAgain:100]; } - (void) test250MultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreadsThenRecordMoreEventsAndSubmitAgain { [self validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreadsThenRecordMoreEventsAndSubmitAgain:250]; } - (void) validateMultipleEventsWithMultipleBatchesWithMultipleSubmitCallsMultipleThreadsThenRecordMoreEventsAndSubmitAgain:(int) numberOfEvents { __block XCTestExpectation *expectation = [self expectationWithDescription:@"Test finished running."]; AWSPinpointConfiguration *config = [[AWSPinpointConfiguration alloc] initWithAppId:self.appIdIAD launchOptions:nil maxStorageSize:AWSPinpointClientByteLimitDefault sessionTimeout:0]; config.userDefaults = [NSUserDefaults standardUserDefaults]; AWSPinpoint *pinpoint = [AWSPinpointEventRecorderTestBase initializePinpointWithConfig:config]; [pinpoint.analyticsClient.eventRecorder setBatchRecordsByteLimit:10]; XCTAssertNotNil(pinpoint.analyticsClient.eventRecorder); AWSPinpointEvent *event = [pinpoint.analyticsClient createEventWithEventType:@"TEST_EVENT_MULTIPLE_SUBMIT"]; [event addAttribute:@"Attr1" forKey:@"Attr1"]; [event addMetric:@(1) forKey:@"Mettr1"]; [self removeAllEventsAndVerify:pinpoint]; for (int i = 0; i < numberOfEvents; i++) { [[[pinpoint.analyticsClient.eventRecorder saveEvent:event] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); return nil; }] waitUntilFinished]; } [[[pinpoint.analyticsClient.eventRecorder getEventsWithLimit:@1000] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); XCTAssertNotNil(task.result); XCTAssertEqual([task.result count], numberOfEvents); //Extract Event and compare event type and timestamp AWSPinpointEvent *resultEvent = [task.result firstObject]; XCTAssertNotNil(resultEvent); XCTAssertTrue([resultEvent.eventType isEqualToString:event.eventType]); XCTAssertEqual(resultEvent.eventTimestamp, event.eventTimestamp); XCTAssertEqual([[resultEvent.allMetrics objectForKey:@"Mettr1"] intValue], @(1).intValue); XCTAssertTrue([[resultEvent.allAttributes objectForKey:@"Attr1"] isEqualToString:@"Attr1"]); return nil; }] waitUntilFinished]; __block int successfulEventsSubmitted = 0; [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); successfulEventsSubmitted += [task.result count]; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); AWSPinpointEvent *secondEvent = [pinpoint.analyticsClient createEventWithEventType:@"SECOND_TEST_EVENT_MULTIPLE_SUBMIT"]; [secondEvent addAttribute:@"Attr1" forKey:@"Attr1"]; [secondEvent addMetric:@(1) forKey:@"Mettr1"]; //Record more events at submission time for (int i = 0; i < numberOfEvents; i++) { [[pinpoint.analyticsClient.eventRecorder saveEvent:secondEvent] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertNil(task.error); [[pinpoint.analyticsClient.eventRecorder submitAllEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { if (!task.error) { XCTAssertTrue([task.result isKindOfClass:[NSArray class]]); successfulEventsSubmitted += [task.result count]; AWSDDLogInfo(@"Submitted batch with %lu events inside.", [task.result count]); [[pinpoint.analyticsClient.eventRecorder getEvents] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) { XCTAssertFalse(pinpoint.analyticsClient.eventRecorder.submissionInProgress); XCTAssertNotNil(task.result); //Should contain no events after submission XCTAssertEqual([task.result count], 0); [expectation fulfill]; return nil; }]; } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; return nil; }]; } } else { AWSDDLogError(@"Did not submit: %@", task.error.userInfo[@"message"]); } return nil; }]; int timeout = numberOfEvents/2; if (timeout < 10) { timeout = 10; } [self waitForExpectationsWithTimeout:(timeout + 1) handler:^(NSError * _Nullable error) { XCTAssertEqual(successfulEventsSubmitted, numberOfEvents * 2); XCTAssertNil(error); }]; [pinpoint destroy]; } @end