blob: 237097d06813889ff49d154ddc6adb40c889358b [file] [log] [blame]
//
// Copyright 2018 Google LLC.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <XCTest/XCTest.h>
#import "Service/Sources/EDOExecutor.h"
#import "Service/Sources/EDOServiceError.h"
@interface EDOExecutorTest : XCTestCase
@property(readonly) void (^emptyBlock)(void);
@end
@implementation EDOExecutorTest
- (void)testExecutorNotRunningToHandleMessageWithoutQueue {
EDOExecutor *executor = [[EDOExecutor alloc] initWithQueue:nil];
NSError *error;
XCTAssertFalse([executor handleBlock:self.emptyBlock error:&error]);
XCTAssertEqualObjects(error.domain, EDOServiceErrorDomain);
XCTAssertEqual(error.code, EDOServiceErrorRequestNotHandled);
XCTAssertTrue([error.userInfo.description containsString:@"execution queue is already released"]);
}
- (void)testExecutorNotRunningToHandleMessageWithQueue {
dispatch_queue_t queue = [self testQueue];
EDOExecutor *executor = [[EDOExecutor alloc] initWithQueue:queue];
__block BOOL executed = NO;
[executor
handleBlock:^{
executed = YES;
}
error:nil];
XCTAssertTrue(executed);
}
- (void)testExecutorFinishRunningAfterClosingMessageQueue {
dispatch_queue_t queue = [self testQueue];
EDOExecutor *executor = [[EDOExecutor alloc] initWithQueue:queue];
XCTestExpectation *expectFinish = [self expectationWithDescription:@"The executor is finished."];
dispatch_async(queue, ^{
[executor loopWithBlock:self.emptyBlock];
// Only fulfills the exepectation after the executor finishes the run.
[expectFinish fulfill];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)testExecutorHandleMessageAfterClosingQueue {
dispatch_queue_t queue = [self testQueue];
EDOExecutor *executor = [[EDOExecutor alloc] initWithQueue:queue];
XCTestExpectation *expectClose = [self expectationWithDescription:@"The queue is closed."];
dispatch_async(queue, ^{
[executor loopWithBlock:^{
[expectClose fulfill];
}];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
[executor handleBlock:self.emptyBlock error:nil];
}
- (void)testSendRequestWithExecutorProcessingStressfully {
NS_VALID_UNTIL_END_OF_SCOPE dispatch_queue_t queue = [self testQueue];
EDOExecutor *executor = [[EDOExecutor alloc] initWithQueue:queue];
XCTestExpectation *expectFinish = [self expectationWithDescription:@"The executor is finished."];
expectFinish.expectedFulfillmentCount = 3;
NSInteger numRuns = 1000;
__block NSInteger numLoops = 0;
dispatch_async(queue, ^{
for (NSInteger i = 0; i < numRuns; ++i) {
[executor loopWithBlock:^{
++numLoops;
}];
}
[expectFinish fulfill];
});
// Generate requests from differnt QoS queues so it can cover cases:
// 1. the request is received before the executor starts
// 2. the request is received after the executor starts but before the while-loop starts
// 3. the request is received after the while-loop tarts.
__block NSInteger numHandlesHighPriority = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
for (NSInteger i = 0; i < numRuns; ++i) {
[executor
handleBlock:^{
++numHandlesHighPriority;
}
error:nil];
}
[expectFinish fulfill];
});
__block NSInteger numHandlesLowPriority = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
for (NSInteger i = 0; i < numRuns; ++i) {
[executor
handleBlock:^{
++numHandlesLowPriority;
}
error:nil];
}
[expectFinish fulfill];
});
[self waitForExpectationsWithTimeout:0.1 * numRuns handler:nil];
XCTAssertEqual(numLoops, numRuns);
XCTAssertEqual(numHandlesHighPriority, numRuns);
XCTAssertEqual(numHandlesLowPriority, numRuns);
}
- (void)testSendRequestWithNestedExecutorProcessingStressfully {
NS_VALID_UNTIL_END_OF_SCOPE dispatch_queue_t queue = [self testQueue];
XCTestExpectation *expectFinish = [self expectationWithDescription:@"The executor is finished."];
const NSInteger numThreadsHighQos = 6;
const NSInteger numThreadsLowQos = 3;
const NSInteger numRuns = 100;
expectFinish.expectedFulfillmentCount = numThreadsHighQos + numThreadsLowQos;
EDOExecutor *executor = [[EDOExecutor alloc] initWithQueue:queue];
void (^handlerBlock)(void) = ^{
[executor loopWithBlock:^{
}];
};
dispatch_async(queue, ^{
[executor loopWithBlock:^{
dispatch_group_t requestsGroup = dispatch_group_create();
for (NSInteger i = 0; i < numThreadsHighQos; ++i) {
dispatch_group_enter(requestsGroup);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
for (NSInteger j = 0; j < numRuns; ++j) {
[executor handleBlock:handlerBlock error:nil];
}
dispatch_group_leave(requestsGroup);
[expectFinish fulfill];
});
}
for (NSInteger i = 0; i < numThreadsLowQos; ++i) {
dispatch_group_enter(requestsGroup);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
for (NSInteger j = 0; j < numRuns; ++j) {
[executor handleBlock:handlerBlock error:nil];
}
dispatch_group_leave(requestsGroup);
[expectFinish fulfill];
});
}
dispatch_group_wait(requestsGroup, DISPATCH_TIME_FOREVER);
}];
});
[self waitForExpectationsWithTimeout:0.1 * numRuns * (numThreadsHighQos + numThreadsLowQos)
handler:nil];
}
#pragma mark - Test helper methods
- (void (^)(void))emptyBlock {
return ^{
};
}
/** Create a dispatch queue with the current testname. */
- (dispatch_queue_t)testQueue {
NSString *queueName = [NSString stringWithFormat:@"com.google.edo.Executor[%@]", self.name];
return dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
}
@end