MultiDeviceSimulatorTests.m [plain text]
//
// MultiDeviceSimulatorTests.m
// MultiDeviceSimulatorTests
//
//
#import <XCTest/XCTest.h>
#import <Foundation/Foundation.h>
#import <Foundation/NSXPCConnection_Private.h>
#import <Security/Security.h>
#import "DeviceSimulatorProtocol.h"
#import "MultiDeviceNetworking.h"
#import <objc/runtime.h>
@interface MDDevice : NSObject<DeviceSimulatorProtocol>
@property NSXPCConnection *connection;
@property NSString *name;
- (instancetype)initWithConnection:(NSXPCConnection *)connection;
@end
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wprotocol"
@implementation MDDevice
- (instancetype)initWithConnection:(NSXPCConnection *)connection
{
self = [super init];
if (self) {
self.connection = connection;
}
return self;
}
/* Oh, ObjC, you are my friend */
- (void)forwardInvocation:(NSInvocation *)invocation
{
struct objc_method_description desc = protocol_getMethodDescription(@protocol(DeviceSimulatorProtocol), [invocation selector], true, true);
if (desc.name == NULL) {
[super forwardInvocation:invocation];
} else {
__block bool dooooooEeeeetExclamationPoint = true;
NSLog(@"forwarding to [ id object = [self.connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
NSLog(@"peer failed with: dooooooEeeeetExclamationPoint = false;
//abort();
}];
if(dooooooEeeeetExclamationPoint) {
[invocation invokeWithTarget:object];
}
}
}
@end
#pragma clang diagnostic pop
@interface MultiDeviceSimulatorTests : XCTestCase <NSXPCListenerDelegate>
@property NSMutableDictionary<NSString *,MDDevice *> *connections;
@property MultiDeviceNetworking *network;
@property MDDevice *masterDevice;
@property NSMutableArray <MDDevice *> *minionDevices;
@end
static NSString *testInstanceUUID;
@implementation MultiDeviceSimulatorTests
+ (void)setUp
{
testInstanceUUID = [[NSUUID UUID] UUIDString];
}
- (void)setUp
{
signal(SIGPIPE, SIG_IGN);
self.connections = [NSMutableDictionary dictionary];
self.network = [[MultiDeviceNetworking alloc] init];
self.minionDevices = [NSMutableArray array];
}
- (void)tearDown
{
__block uint64_t totalUserUsec = 0, totalSysUsec = 0, totalDiskUsage = 0;
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (NSString *name in self.connections) {
MDDevice *device = self.connections[name];
NSLog(@"device: [device diagnosticsCPUUsage:^(bool success, uint64_t user_usec, uint64_t sys_usec, NSError *error) {
NSLog(@"cpu totalUserUsec += user_usec;
totalSysUsec += sys_usec;
result[[NSString stringWithFormat:@"cpu- }];
[device diagnosticsDiskUsage:^(bool success, uint64_t usage, NSError *error) {
NSLog(@"disk totalDiskUsage += usage;
result[[NSString stringWithFormat:@"disk- }];
}
for(MDDevice *dev in self.minionDevices) {
[dev sosLeaveCircle:^(bool success, NSError *error) {
;
}];
}
[self.masterDevice sosLeaveCircle:^(bool success, NSError *error) {
;
}];
self.minionDevices = NULL;
self.masterDevice = NULL;
result[@"cpu-total"] = @{ @"user_usec" : @(totalUserUsec), @"system_usec" : @(totalSysUsec)};
result[@"disk-total"] = @{ @"disk" : @(totalDiskUsage) };
NSLog(@"Total cpu: u: NSLog(@"Total disk:
/* XXX check for leaks in all devices */
for (NSString *name in self.connections) {
MDDevice *device = self.connections[name];
[device.connection invalidate];
}
self.connections = NULL;
[self.network dumpKVSState];
[self.network dumpCounters];
[self.network disconnectAll];
[self.network clearTestExpectations];
self.network = NULL;
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:result options:0 error:NULL];
[jsonData writeToFile:[NSString stringWithFormat:@"/tmp/test-result-
}
- (void)setupMasterDevice
{
self.masterDevice = [self device:@"ipad" model:@"iPad" version:@"15E143a"];
[self.masterDevice setupSOSCircle:@"user" password:@"foo" complete:^void(bool success, NSError *error) {
XCTAssert(success, "Expect success: }];
[self.masterDevice sosCircleStatus:^void(SOSCCStatus status, NSError *error) {
XCTAssertEqual(status, kSOSCCInCircle, @"expected to be in circle: }];
}
//MARK: - Device logic
- (MDDevice *)device:(NSString *)name model:(NSString *)model version:(NSString *)version {
MDDevice *device = self.connections[name];
if (device != NULL) {
return NULL;
}
NSXPCConnection *conn = [[NSXPCConnection alloc] initWithServiceName:@"com.apple.Security.DeviceSimulator"];
conn.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(DeviceSimulatorProtocol)];
[conn _setUUID:[NSUUID UUID]]; // select a random instance
[conn resume];
device = [[MDDevice alloc] initWithConnection:conn];
device.name = name;
self.connections[name] = device;
[device setDevice:name
version:version
model:model
testInstance:testInstanceUUID
network:[self.network endpoint]
complete:^(BOOL success) {
if (!success) {
abort();
}
}];
return device;
}
- (bool)addDeviceToCircle: (MDDevice *) dev {
__block bool added = false;
__block NSString *devPeerID = NULL;
__block NSError *localErr = nil;
[dev setupSOSCircle:@"user" password:@"foo" complete:^void(bool success, NSError *error) {
XCTAssert(success, "Expect success: added = success;
}];
if(added) {
[dev sosRequestToJoin:^(bool success, NSString *peerID, NSError *error) {
XCTAssert(success, "Expect success: XCTAssertNotEqual(peerID, NULL, "Expected to find peerID for peer2");
devPeerID = peerID;
added &= success;
}];
}
if(added) {
__block bool done = false;
for(int tries=0; tries < 5; tries++) {
sleep(2);
[self.masterDevice sosApprovePeer:devPeerID complete:^(BOOL success, NSError *error) {
localErr = [error copy];
if(success) {
localErr = nil;
done = true;
}
}];
if(done) break;
}
added &= done;
}
XCTAssert(added, "Expect success (for approve of return added;
}
- (void)addKeychainItems:(unsigned long)items toDevice:(MDDevice *)device
{
NSDictionary *addItem = @{
(__bridge id)kSecClass :(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecValueData : [@"foo" dataUsingEncoding:NSUTF8StringEncoding],
(__bridge id)kSecAttrAccessGroup: @"com.apple.cfnetwork",
(__bridge id)kSecAttrSyncViewHint: @"PCS-MasterKey",
(__bridge id)kSecAttrDescription: @"delete me if found",
(__bridge id)kSecAttrServer: @"server",
(__bridge id)kSecAttrAccount: @"account",
(__bridge id)kSecAttrSynchronizable: @YES,
(__bridge id)kSecAttrPath: @"/path",
(__bridge id)kSecAttrIsInvisible: @YES,
};
[device secItemAdd:addItem complete:^void(OSStatus status, NSDictionary *result) {
NSLog(@"Result string was dev1: XCTAssertEqual(status, 0, "Expect success");
}];
}
- (void)runSigninWithAdditionalDevices:(unsigned)additionalDeviceCount keychainItems:(unsigned long)items {
for (unsigned n = 0; n < additionalDeviceCount; n++) {
MDDevice *dev = [self device:[NSString stringWithFormat:@"mac- if(dev) {
[self addDeviceToCircle: dev];
[self.minionDevices addObject:dev];
}
}
if (items) {
[self addKeychainItems:items toDevice:self.masterDevice];
}
}
- (void)testPref3Devices {
[self setupMasterDevice];
[self measureBlock:^{
[self runSigninWithAdditionalDevices:2 keychainItems:0];
}];
}
#if 0 /* disabled because of 10min time limit in bats (for now) */
- (void)testPref3Devices1 {
[self setupMasterDevice];
[self runSigninWithAdditionalDevices:2 keychainItems:1];
sleep(60);
}
- (void)testPref3Devices10 {
[self setupMasterDevice];
[self runSigninWithAdditionalDevices:2 keychainItems:10];
sleep(60);
}
- (void)testPref3Devices100 {
[self setupMasterDevice];
[self runSigninWithAdditionalDevices:2 keychainItems:100];
sleep(60);
}
- (void)testPref3Devices1000 {
[self setupMasterDevice];
[self runSigninWithAdditionalDevices:2 keychainItems:1000];
sleep(60);
sleep(1);
}
- (void)testPref6Devices {
[self setupMasterDevice];
[self measureBlock:^{
[self runSigninWithAdditionalDevices:5 keychainItems:0];
}];
}
- (void) testDevices6Retired {
[self setupMasterDevice];
[self measureBlock:^{
[self runSigninWithAdditionalDevices:5 keychainItems:0];
}];
}
#endif
- (void)test2Device {
NSLog(@"create devices");
MDDevice *dev1 = [self device:@"ipad" model:@"iPad" version:@"15E143a"];
MDDevice *dev2 = [self device:@"mac" model:@"Mac Pro" version:@"17E121"];
/*
* using PCS-MasterKey for direct syncing during inital sync
*/
NSDictionary *addItem = @{
(__bridge id)kSecClass :(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecValueData : [@"foo" dataUsingEncoding:NSUTF8StringEncoding],
(__bridge id)kSecAttrAccessGroup: @"com.apple.cfnetwork",
(__bridge id)kSecAttrSyncViewHint: @"PCS-MasterKey",
(__bridge id)kSecAttrDescription: @"delete me if found",
(__bridge id)kSecAttrServer: @"server",
(__bridge id)kSecAttrAccount: @"account",
(__bridge id)kSecAttrSynchronizable: @YES,
(__bridge id)kSecAttrPath: @"/path",
(__bridge id)kSecAttrIsInvisible: @YES,
};
NSDictionary *findItem = @{
(__bridge id)kSecClass :(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecAttrAccessGroup: @"com.apple.cfnetwork",
(__bridge id)kSecAttrServer: @"server",
(__bridge id)kSecAttrAccount: @"account",
(__bridge id)kSecAttrSynchronizable: @YES,
};
[dev1 secItemAdd:addItem complete:^void(OSStatus status, NSDictionary *result) {
NSLog(@"Result string was dev1: XCTAssertEqual(status, 0, "Expect success");
}];
[dev1 secItemCopyMatching:findItem complete:^(OSStatus status, NSArray<NSDictionary *> *result) {
NSLog(@"Result string was dev1: XCTAssertEqual(status, 0, "Expect success");
}];
/*
* Setup and validate device 1
*/
XCTestExpectation *expection = [self expectationWithDescription:@"expect to create circle"];
[dev1 setupSOSCircle:@"user" password:@"foo" complete:^void(bool success, NSError *error) {
XCTAssert(success, "Expect success: [expection fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
[dev1 sosCircleStatus:^void(SOSCCStatus status, NSError *error) {
XCTAssertEqual(status, kSOSCCInCircle, @"expected to be in circle: }];
[dev2 sosCircleStatus:^void(SOSCCStatus status, NSError *error) {
XCTAssertEqual(status, kSOSCCError, @"expected to be in error: }];
/*
* Setup and validate device 2
*/
expection = [self expectationWithDescription:@"expect to create circle"];
[dev2 setupSOSCircle:@"user" password:@"foo" complete:^void(bool success, NSError *error) {
XCTAssert(success, "Expect success: [expection fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
[dev1 sosCircleStatus:^void(SOSCCStatus status, NSError *error) {
XCTAssertEqual(status, kSOSCCInCircle, @"expected to be in circle: }];
[dev2 sosCircleStatus:^void(SOSCCStatus status, NSError *error) {
XCTAssertEqual(status, kSOSCCNotInCircle, @"expected to be NOT in circle: }];
NSLog(@"Update all views (dev1)");
expection = [self expectationWithDescription:@"expect circle update"];
expection.assertForOverFulfill = false;
[self.network setTestExpectation:expection forKey:@"oak"];
expection.assertForOverFulfill = false;
[dev1 sosEnableAllViews:^(BOOL success, NSError *error) {
XCTAssert(success, "Expect success: }];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
[self.network clearTestExpectations];
__block NSString *peerID1 = NULL;
[dev1 sosPeerID:^(NSString *peerID) {
XCTAssertNotEqual(peerID, NULL, @"expected to find a peerID for peer1");
peerID1 = peerID;
}];
[dev2 sosPeerID:^(NSString *peerID) {
XCTAssertEqual(peerID, NULL, @"expected to NOT find peerID for peer2");
}];
[dev1 sosPeerID:^(NSString *peerID) {
XCTAssertNotEqual(peerID, NULL, @"expected to find a peerID for peer1");
XCTAssertEqualObjects(peerID1, peerID, "dev1 changed ?");
}];
/*
* Validate second device can request to join
*/
expection = [self expectationWithDescription:@"expect circle update"];
expection.assertForOverFulfill = false;
[self.network setTestExpectation:expection forKey:@"oak"];
__block NSString *peerID2 = NULL;
[dev2 sosRequestToJoin:^(bool success, NSString *peerID, NSError *error) {
XCTAssert(success, "Expect success: XCTAssertNotEqual(peerID, NULL, @"expected to find peerID for peer2");
peerID2 = peerID;
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
[self.network clearTestExpectations];
/*
* Check that device 2 can't self join
*/
expection = [self expectationWithDescription:@"expect device 2 can't self join"];
[dev2 sosApprovePeer:peerID2 complete:^(BOOL success, NSError *error) {
XCTAssert(!success, "Expect failure: [expection fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
[dev2 sosCircleStatus:^void(SOSCCStatus status, NSError *error) {
XCTAssertEqual(status, kSOSCCRequestPending, @"expected to be pending request: }];
[dev1 sosPeerID:^(NSString *peerID) {
XCTAssertNotEqual(peerID, NULL, @"expected to find a peerID for peer1");
XCTAssertEqualObjects(peerID1, peerID, "dev1 changed ?");
}];
/*
* Approve device 2 and enable all views
*/
NSLog(@"approve");
expection = [self expectationWithDescription:@"expect circle update"];
expection.assertForOverFulfill = false;
[self.network setTestExpectation:expection forKey:@"oak"];
[dev1 sosApprovePeer:peerID2 complete:^(BOOL success, NSError *error) {
XCTAssert(success, "Expect success: }];
[self waitForExpectationsWithTimeout:60.0 handler:nil];
[self.network clearTestExpectations];
/*
* Validate device 2 made it into circle and have a peerID
*/
[dev1 sosCircleStatus:^void(SOSCCStatus status, NSError *error) {
XCTAssertEqual(status, kSOSCCInCircle, @"expected to be in circle: }];
[dev2 sosCircleStatus:^void(SOSCCStatus status, NSError *error) {
XCTAssertEqual(status, kSOSCCInCircle, @"expected to be in circle: }];
[dev2 sosPeerID:^(NSString *peerID) {
XCTAssertNotEqual(peerID, NULL, @"expected to find a peerID for peer2");
peerID2 = peerID;
//XCTAssertEqualObject(peerID1, peerID2, "expect peerID to be different");
}];
/*
* Enable view for syncing
*/
NSString *netID1toID2 = [NSString stringWithFormat:@"ak| NSString *netID2toID1 = [NSString stringWithFormat:@"ak|
expection = [self expectationWithDescription:@"expect traffic from 1->2"];
expection.assertForOverFulfill = false;
[self.network setTestExpectation:expection forKey:netID1toID2];
expection = [self expectationWithDescription:@"expect traffic from 2->1"];
expection.assertForOverFulfill = false;
[self.network setTestExpectation:expection forKey:netID2toID1];
expection = [self expectationWithDescription:@"expect circle update"];
expection.assertForOverFulfill = false;
[self.network setTestExpectation:expection forKey:@"oak"];
/*
* Perform initial sync
*/
NSLog(@"initial sync");
expection = [self expectationWithDescription:@"perform initial sync"];
[dev2 sosWaitForInitialSync:^(bool success, NSError *error) {
XCTAssert(success, "Expect success for syncing: [expection fulfill];
}];
/*
*
*/
NSLog(@"Update all views (dev2)");
[dev2 sosEnableAllViews:^(BOOL success, NSError *error) {
XCTAssert(success, "Expect success: }];
[self waitForExpectationsWithTimeout:60.0 handler:nil];
[self.network clearTestExpectations];
/*
* check syncing did its thing
*/
NSLog(@"SecItemCopyMatching");
[dev1 secItemCopyMatching:findItem complete:^(OSStatus status, NSArray<NSDictionary *> *result) {
NSLog(@"Result string was dev1: XCTAssertEqual(status, 0, "Expect success");
}];
[dev2 secItemCopyMatching:findItem complete:^(OSStatus status, NSArray<NSDictionary *> *result) {
NSLog(@"Result string was dev2: XCTAssertEqual(status, 0, "Expect success");
}];
NSLog(@"done");
}
@end