SCTestDynamicStore.m   [plain text]

/*
* Copyright (c) 2016, 2017 Apple Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/

#import "SCTest.h"
#import "SCTestUtils.h"

@interface SCTestDynamicStore : SCTest
@property SCDynamicStoreRef store;
@property dispatch_semaphore_t sem;
@property int counter;
@end

@implementation SCTestDynamicStore

+ (NSString *)command
{
return @"dynamic_store";
}

+ (NSString *)commandDescription
{
return @"Tests the SCDynamicStore code path";
}

- (instancetype)initWithOptions:(NSDictionary *)options
{
self = [super initWithOptions:options];
if (self) {
_store = SCDynamicStoreCreate(kCFAllocatorDefault,
CFSTR("SCTest"),
NULL,
NULL);
if (_store == NULL) {
SCTestLog("Could not create session");
ERR_EXIT;
}
}
return self;
}

- (void)dealloc
{
if (self.store != NULL) {
CFRelease(self.store);
self.store = NULL;
}
}

- (void)start
{
CFStringRef key;
if (self.options[kSCTestDynamicStoreOptionDNS]) {
key = SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault, kSCDynamicStoreDomainState, kSCEntNetDNS);
CFPropertyListRef value = SCDynamicStoreCopyValue(self.store, key);
SCTestLog("%@ : %@", key, value);
CFRelease(key);
}

if (self.options[kSCTestDynamicStoreOptionIPv4]) {
key = SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault, kSCDynamicStoreDomainState, kSCEntNetIPv4);
CFPropertyListRef value = SCDynamicStoreCopyValue(self.store, key);
SCTestLog("%@ : %@", key, value);
CFRelease(key);
}

if (self.options[kSCTestDynamicStoreOptionIPv6]) {
key = SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault, kSCDynamicStoreDomainState, kSCEntNetIPv6);
CFPropertyListRef value = SCDynamicStoreCopyValue(self.store, key);
SCTestLog("%@ : %@", key, value);
CFRelease(key);
}

if (self.options[kSCTestDynamicStoreOptionProxies]) {
key = SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault, kSCDynamicStoreDomainState, kSCEntNetProxies);
CFPropertyListRef value = SCDynamicStoreCopyValue(self.store, key);
SCTestLog("%@ : %@", key, value);
CFRelease(key);
}

[self cleanupAndExitWithErrorCode:0];
}

- (void)cleanupAndExitWithErrorCode:(int)error
{
[super cleanupAndExitWithErrorCode:error];
}

- (BOOL)setup
{
return YES;
}

- (BOOL)unitTest
{
if(![self setup]) {
return NO;
}

BOOL allUnitTestsPassed = YES;
allUnitTestsPassed &= [self unitTestSetAndRemoveValue];
allUnitTestsPassed &= [self unitTestCopyMultiple];
allUnitTestsPassed &= [self unitTestSCDynamicStoreCallbackStress];
allUnitTestsPassed &= [self unitTestSCDynamicStoreSetMultipleStress];

if(![self tearDown]) {
return NO;
}

return allUnitTestsPassed;
}

- (BOOL)tearDown
{
return YES;
}

- (BOOL)unitTestSetAndRemoveValue
{
int iterations = 1000;
NSDictionary *bogusValue;
SCTestDynamicStore *test;

test = [[SCTestDynamicStore alloc] initWithOptions:self.options];
bogusValue = @{@"Pretty":@"Useless"};

for (int i = 0; i < iterations; i++) {
NSUUID *uuid = [NSUUID UUID];
CFStringRef key;
BOOL ok;

key = SCDynamicStoreKeyCreateNetworkServiceEntity(kCFAllocatorDefault,
CFSTR("SCTest"),
(__bridge CFStringRef)uuid.UUIDString,
kSCEntNetDNS);
ok = SCDynamicStoreSetValue(test.store, key, (__bridge CFDictionaryRef)bogusValue);
if (!ok) {
SCTestLog("Failed to set value in SCDynamicStore. Error: %s", SCErrorString(SCError()));
CFRelease(key);
return NO;
}

ok = SCDynamicStoreRemoveValue(test.store, key);
if (!ok) {
SCTestLog("Failed to remove value from SCDynamicStore. Error: %s", SCErrorString(SCError()));
CFRelease(key);
return NO;
}

CFRelease(key);
}

SCTestLog("Successfully completed setAndRemove unit test");
return YES;
}

- (BOOL)unitTestCopyMultiple
{
NSString *pattern;
SCTestDynamicStore *test;
CFArrayRef keyList;
int iterations = 1000;

test = [[SCTestDynamicStore alloc] initWithOptions:self.options];
pattern = (__bridge_transfer NSString *)SCDynamicStoreKeyCreate(kCFAllocatorDefault,
CFSTR("%@/%@/%@/%@/%@"),
kSCDynamicStoreDomainState,
kSCCompNetwork,
kSCCompInterface,
kSCCompAnyRegex,
kSCCompAnyRegex);

keyList = SCDynamicStoreCopyKeyList(test.store, (__bridge CFStringRef)pattern);
for (int i = 0; i < iterations; i++) {
CFDictionaryRef value = SCDynamicStoreCopyMultiple(test.store, keyList, NULL);
if (value == NULL) {
SCTestLog("Failed to copy multiple values from SCDynamicStore. Error: %s", SCErrorString(SCError()));
CFRelease(keyList);
return NO;
}
CFRelease(value);
}
CFRelease(keyList);

pattern = (__bridge_transfer NSString *)SCDynamicStoreKeyCreate(kCFAllocatorDefault,
CFSTR("%@/%@/%@/%@/%@"),
kSCDynamicStoreDomainSetup,
kSCCompNetwork,
kSCCompService,
kSCCompAnyRegex,
kSCCompAnyRegex);

keyList = SCDynamicStoreCopyKeyList(test.store, (__bridge CFStringRef)pattern);
for (int i = 0; i < iterations; i++) {
CFDictionaryRef value = SCDynamicStoreCopyMultiple(test.store, keyList, NULL);
if (value == NULL) {
SCTestLog("Failed to copy multiple values from SCDynamicStore. Error: %s", SCErrorString(SCError()));
CFRelease(keyList);
return NO;
}
CFRelease(value);
}
CFRelease(keyList);

SCTestLog("Successfully completed copyMultiple unit test");
return YES;
}

void
myTestCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void *ctx)
{
#pragma unused(store)
#pragma unused(changedKeys)
SCTestDynamicStore *test = (__bridge SCTestDynamicStore *)ctx;
test.counter++;
if (test.sem != NULL) {
dispatch_semaphore_signal(test.sem);
}
}

- (BOOL)unitTestSCDynamicStoreCallbackStress
{
SCTestDynamicStore *test;
int iterations = 100;
NSString *testKey = @"State:/myTestKey";
BOOL ok;
dispatch_queue_t callbackQ;

test = [[SCTestDynamicStore alloc] initWithOptions:self.options];
if (test.store != NULL) {
CFRelease(test.store);
test.store = NULL;
}

SCDynamicStoreContext ctx = {0, (__bridge void * _Nullable)(test), CFRetain, CFRelease, NULL};
test.store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("SCTest"), myTestCallback, &ctx);

ok = SCDynamicStoreSetNotificationKeys(test.store, (__bridge CFArrayRef)@[testKey], NULL);
if (!ok) {
SCTestLog("Failed to set notification keys. Error: %s", SCErrorString(SCError()));
return NO;
}

callbackQ = dispatch_queue_create("SCTestDynamicStore callback queue", NULL);
ok = SCDynamicStoreSetDispatchQueue(test.store, callbackQ);
if (!ok) {
SCTestLog("Failed to set dispatch queue. Error: %s", SCErrorString(SCError()));
return NO;
}

for (int i = 0; i < iterations; i++) {
NSUUID *uuid = [NSUUID UUID];
ok = SCDynamicStoreSetValue(test.store, (__bridge CFStringRef)testKey, (__bridge CFStringRef)uuid.UUIDString);
if (!ok) {
SCTestLog("Failed to set value. Error: %s", SCErrorString(SCError()));
return NO;
}
// Perform a write to Dynamic Store every 10ms.
[test waitFor:0.01];
}

ok = SCDynamicStoreRemoveValue(test.store, (__bridge CFStringRef)testKey);
if (!ok) {
SCTestLog("Failed to remove value. Error: %s", SCErrorString(SCError()));
return NO;
}

if (test.counter < iterations) {
// Not all callbacks were received
SCTestLog("Not all SCDynamicStore callbacks were received (%d/%d). Something went wrong", test.counter, iterations);
return NO;
}

SCTestLog("Successfully completed SCDynamicStore callbacks unit test");
return YES;
}

- (BOOL)unitTestSCDynamicStoreSetMultipleStress
{
SCTestDynamicStore *test;
int iterations = 100;
NSMutableArray *testKeyArray;
NSMutableDictionary *testSetDictionary;
int expectedCallbackCount = 0;
BOOL ok;
dispatch_queue_t callbackQ;
uint8_t waitTime = 1; // second

test = [[SCTestDynamicStore alloc] initWithOptions:self.options];
if (test.store != NULL) {
CFRelease(test.store);
test.store = NULL;
}

SCDynamicStoreContext ctx = {0, (__bridge void * _Nullable)(test), CFRetain, CFRelease, NULL};
test.store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("SCTest"), myTestCallback, &ctx);
test.sem = dispatch_semaphore_create(0);

testKeyArray = [[NSMutableArray alloc] init];

for (int i = 0; i < iterations; i++) {
NSUUID *uuid = [NSUUID UUID];
NSString *str = [NSString stringWithFormat:@"State:/%@", uuid];
[testKeyArray addObject:str];
}

testSetDictionary = [[NSMutableDictionary alloc] init];
for (NSString *key in testKeyArray) {
NSUUID *uuid = [NSUUID UUID];
[testSetDictionary setObject:@[uuid.UUIDString] forKey:key];
}

callbackQ = dispatch_queue_create("SCTestDynamicStore callback queue", NULL);
ok = SCDynamicStoreSetNotificationKeys(test.store, (__bridge CFArrayRef)testKeyArray, NULL);
if (!ok) {
SCTestLog("Failed to set notification keys. Error: %s", SCErrorString(SCError()));
return NO;
}

ok = SCDynamicStoreSetDispatchQueue(test.store, callbackQ);
if (!ok) {
SCTestLog("Failed to set dispatch queue. Error: %s", SCErrorString(SCError()));
return NO;
}

ok = SCDynamicStoreSetMultiple(test.store, (__bridge CFDictionaryRef)testSetDictionary, NULL, NULL);
if (!ok) {
SCTestLog("Failed to set multiple keys. Error: %s", SCErrorString(SCError()));
return NO;
} else {
expectedCallbackCount++; // One callback for set multiple
}

if(dispatch_semaphore_wait(test.sem, dispatch_time(DISPATCH_TIME_NOW, waitTime * NSEC_PER_SEC))) {
SCTestLog("Failed to get a callback when multiple keys are set");
return NO;
}

ok = SCDynamicStoreSetMultiple(test.store, NULL, NULL, (__bridge CFArrayRef)testKeyArray);
if (!ok) {
SCTestLog("Failed to set multiple keys. Error: %s", SCErrorString(SCError()));
return NO;
} else {
expectedCallbackCount++; // One callback for fake notification
}

if(dispatch_semaphore_wait(test.sem, dispatch_time(DISPATCH_TIME_NOW, waitTime * NSEC_PER_SEC))) {
SCTestLog("Failed to get a callback when multiple keys are notified");
return NO;
}

ok = SCDynamicStoreSetMultiple(test.store, NULL, (__bridge CFArrayRef)testKeyArray, NULL);
if (!ok) {
SCTestLog("Failed to set multiple keys. Error: %s", SCErrorString(SCError()));
return NO;
} else {
expectedCallbackCount++; // One callback for key removal
}

if(dispatch_semaphore_wait(test.sem, dispatch_time(DISPATCH_TIME_NOW, waitTime * NSEC_PER_SEC))) {
SCTestLog("Failed to get a callback when multiple keys are removed");
return NO;
}

if (test.counter < expectedCallbackCount) {
// Not all callbacks were received
SCTestLog("Not all SCDynamicStore callbacks were received (%d/%d). Something went wrong", test.counter, expectedCallbackCount);
return NO;
}

SCTestLog("Successfully completed SCDynamicStore set multiple unit test");
return YES;

}

@end