diff --git a/iOS/FlipperKitTestUtils/FlipperClient+Testing.h b/iOS/FlipperKit/FlipperClient+Testing.h similarity index 90% rename from iOS/FlipperKitTestUtils/FlipperClient+Testing.h rename to iOS/FlipperKit/FlipperClient+Testing.h index cd19dce45..d7aa6c0a5 100644 --- a/iOS/FlipperKitTestUtils/FlipperClient+Testing.h +++ b/iOS/FlipperKit/FlipperClient+Testing.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. * * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. diff --git a/iOS/FlipperKit/FlipperClient.mm b/iOS/FlipperKit/FlipperClient.mm index cdf74dc20..c74bf9bf8 100644 --- a/iOS/FlipperKit/FlipperClient.mm +++ b/iOS/FlipperKit/FlipperClient.mm @@ -15,6 +15,7 @@ #import #include "SKStateUpdateCPPWrapper.h" #import "FlipperDiagnosticsViewController.h" +#import "FlipperClient+Testing.h" #if !TARGET_OS_SIMULATOR //#import "SKPortForwardingServer.h" @@ -177,4 +178,15 @@ using WrapperPlugin = facebook::flipper::FlipperCppWrapperPlugin; @end +@implementation FlipperClient (Testing) + +- (instancetype)initWithCppClient:(facebook::flipper::FlipperClient *)cppClient { + if (self = [super init]) { + _cppClient = cppClient; + } + return self; +} + +@end + #endif diff --git a/iOS/FlipperKitTestUtils/BlockBasedSonarPlugin.h b/iOS/FlipperKitTestUtils/BlockBasedSonarPlugin.h index 72b5411bf..4e17eda6f 100644 --- a/iOS/FlipperKitTestUtils/BlockBasedSonarPlugin.h +++ b/iOS/FlipperKitTestUtils/BlockBasedSonarPlugin.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. * * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. @@ -17,5 +17,6 @@ typedef void (^DisconnectBlock)(); @interface BlockBasedSonarPlugin : NSObject - (instancetype)initIdentifier:(NSString *)identifier connect:(ConnectBlock)connect disconnect:(DisconnectBlock)disconnect; +- (instancetype)initIdentifier:(NSString *)identifier connect:(ConnectBlock)connect disconnect:(DisconnectBlock)disconnect runInBackground:(BOOL)runInBackground; @end diff --git a/iOS/FlipperKitTestUtils/BlockBasedSonarPlugin.m b/iOS/FlipperKitTestUtils/BlockBasedSonarPlugin.m index 4acd07a96..d1bb7f062 100644 --- a/iOS/FlipperKitTestUtils/BlockBasedSonarPlugin.m +++ b/iOS/FlipperKitTestUtils/BlockBasedSonarPlugin.m @@ -12,6 +12,7 @@ NSString *_identifier; ConnectBlock _connect; DisconnectBlock _disconnect; + BOOL _runInBackground; } - (instancetype)initIdentifier:(NSString *)identifier connect:(ConnectBlock)connect disconnect:(DisconnectBlock)disconnect @@ -20,10 +21,21 @@ _identifier = identifier; _connect = connect; _disconnect = disconnect; + _runInBackground = false; } return self; } +- (instancetype)initIdentifier:(NSString *)identifier connect:(ConnectBlock)connect disconnect:(DisconnectBlock)disconnect runInBackground:(BOOL)runInBackground { + if (self = [super init]) { + _identifier = identifier; + _connect = connect; + _disconnect = disconnect; + _runInBackground = runInBackground; + } + return self; +} + - (NSString *)identifier { return _identifier; @@ -38,9 +50,13 @@ - (void)didDisconnect { - if (_connect) { + if (_disconnect) { _disconnect(); } } +- (BOOL)runInBackground { + return _runInBackground; +} + @end diff --git a/iOS/FlipperKitTests/FlipperClientTests.mm b/iOS/FlipperKitTests/FlipperClientTests.mm new file mode 100644 index 000000000..3cb52c613 --- /dev/null +++ b/iOS/FlipperKitTests/FlipperClientTests.mm @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +#import +#if FB_SONARKIT_ENABLED + +#import +#import +#import +#import +#import +#import +#import +#import + +@interface FlipperClientTests : XCTestCase + +@end + +@implementation FlipperClientTests +facebook::flipper::FlipperClient *client; +facebook::flipper::test::FlipperConnectionManagerMock *socket; +FlipperClient *objcClient; + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. + socket = new facebook::flipper::test::FlipperConnectionManagerMock; + auto state = std::make_shared(); + + client = new facebook::flipper::FlipperClient(std::unique_ptr{socket}, state); + objcClient = [[FlipperClient alloc] initWithCppClient:client]; + +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + delete client; +} + +- (void)testGetPlugin { + + BlockBasedSonarPlugin *cat = [[BlockBasedSonarPlugin alloc] initIdentifier:@"cat" connect:nil disconnect:nil]; + BlockBasedSonarPlugin *dog = [[BlockBasedSonarPlugin alloc] initIdentifier:@"dog" connect:nil disconnect:nil]; + + [objcClient addPlugin:cat]; + [objcClient addPlugin:dog]; + + NSObject *retrievedPlugin = [objcClient pluginWithIdentifier:@"cat"]; + XCTAssertEqual(retrievedPlugin, cat); + retrievedPlugin = [objcClient pluginWithIdentifier:@"dog"]; + XCTAssertEqual(retrievedPlugin, dog); +} + + +- (void)testRemovePlugin { + BlockBasedSonarPlugin *cat = [[BlockBasedSonarPlugin alloc] initIdentifier:@"cat" connect:nil disconnect:nil]; + + [objcClient addPlugin:cat]; + [objcClient removePlugin:cat]; + + folly::dynamic message = folly::dynamic::object("id", 1)("method", "getPlugins"); + socket->callbacks->onMessageReceived(message); + folly::dynamic expected = folly::dynamic::object("id", 1)("success", folly::dynamic::object("plugins", folly::dynamic::array())); + XCTAssertEqual(socket->messages.back(), expected); +} + +- (void) testPluginActivatedInBackgroundMode { + __block BOOL pluginConnected = NO; + BlockBasedSonarPlugin *cat = [[BlockBasedSonarPlugin alloc] initIdentifier:@"cat" connect:^(id) { + pluginConnected = YES; + } disconnect:^{ + pluginConnected = NO; + + } runInBackground: YES]; + + [objcClient addPlugin:cat]; + [objcClient start]; + XCTAssertTrue(pluginConnected); +} + +- (void) testPluginNotActivatedInNonBackgroundMode { + __block BOOL pluginConnected = NO; + BlockBasedSonarPlugin *cat = [[BlockBasedSonarPlugin alloc] initIdentifier:@"cat" connect:^(id) { + pluginConnected = YES; + } disconnect:^{ + pluginConnected = NO; + + } runInBackground: NO]; + + [objcClient addPlugin:cat]; + [objcClient start]; + XCTAssertFalse(pluginConnected); +} + +- (void)testConnectAndDisconnectCallbackForNonBackgroundCase { + __block BOOL pluginConnected = NO; + BlockBasedSonarPlugin *cat = [[BlockBasedSonarPlugin alloc] initIdentifier:@"cat" connect:^(id) { + pluginConnected = YES; + } disconnect:^{ + pluginConnected = NO; + } runInBackground: NO]; + + [objcClient addPlugin:cat]; + [objcClient start]; + + folly::dynamic messageInit = folly::dynamic::object("method", "init")("params", folly::dynamic::object("plugin", "cat")); + socket->callbacks->onMessageReceived(messageInit); + XCTAssertTrue(pluginConnected); + [objcClient stop]; + XCTAssertFalse(pluginConnected); +} + +- (void)testConnectAndDisconnectCallbackForBackgroundCase { + __block BOOL pluginConnected = YES; + BlockBasedSonarPlugin *cat = [[BlockBasedSonarPlugin alloc] initIdentifier:@"cat" connect:^(id) { + pluginConnected = YES; + } disconnect:^{ + pluginConnected = NO; + } runInBackground: YES]; + + [objcClient addPlugin:cat]; + [objcClient start]; + XCTAssertTrue(pluginConnected); + [objcClient stop]; + XCTAssertFalse(pluginConnected); +} + +- (void)testCrashSuppressionInDidConnectCallback { + __block BOOL pluginConnected = NO; + BlockBasedSonarPlugin *cat = [[BlockBasedSonarPlugin alloc] initIdentifier:@"cat" connect:^(id) { + pluginConnected = YES; + NSArray *array = @[]; + [array objectAtIndex:10]; //This will throw an exception + } disconnect:nil runInBackground: YES]; + + [objcClient addPlugin:cat]; + // Since background plugin's didconnect is called as soon as flipper client starts + XCTAssertNoThrow([objcClient start]); + XCTAssertTrue(pluginConnected); // To be sure that connect block is called +} + +- (void)testCrashSuppressionInDisconnectCallback { + __block BOOL isCalled = NO; + BlockBasedSonarPlugin *cat = [[BlockBasedSonarPlugin alloc] initIdentifier:@"cat" connect:nil disconnect:^{ + isCalled = YES; + NSArray *array = @[]; + [array objectAtIndex:10]; //This will throw an exception + } runInBackground: YES]; + + [objcClient addPlugin:cat]; + [objcClient start]; + + XCTAssertNoThrow([objcClient stop]); // Stopping client will call disconnect of the plugin + XCTAssertTrue(isCalled); // To be sure that connect block is called +} + +- (void)testMethodBlockIsCalledNonBackgroundCase { + __block BOOL isCalled = NO; + + BlockBasedSonarPlugin *cat = [[BlockBasedSonarPlugin alloc] initIdentifier:@"PluginIdentifier" connect:^(id connection) { + + [connection receive:@"MethodName" withBlock:^(NSDictionary * dict, id responder) { + isCalled = YES; + }]; + } disconnect:nil]; + + [objcClient addPlugin:cat]; + [objcClient start]; + + folly::dynamic messageInit = folly::dynamic::object("method", "init")("params", folly::dynamic::object("plugin", "PluginIdentifier")); + socket->callbacks->onMessageReceived(messageInit); + folly::dynamic message = folly::dynamic::object("id", 1)("method", "execute")("params", folly::dynamic::object("api", "PluginIdentifier")("method", "MethodName")); + socket->callbacks->onMessageReceived(message); + + XCTAssertTrue(isCalled); +} + +- (void)testMethodBlockIsCalledBackgroundCase { + __block BOOL isCalled = NO; + + BlockBasedSonarPlugin *cat = [[BlockBasedSonarPlugin alloc] initIdentifier:@"PluginIdentifier" connect:^(id connection) { + + [connection receive:@"MethodName" withBlock:^(NSDictionary * dict, id responder) { + isCalled = YES; + }]; + } disconnect:nil runInBackground:YES]; + + [objcClient addPlugin:cat]; + [objcClient start]; + + folly::dynamic message = folly::dynamic::object("id", 1)("method", "execute")("params", folly::dynamic::object("api", "PluginIdentifier")("method", "MethodName")); + socket->callbacks->onMessageReceived(message); + + XCTAssertTrue(isCalled); +} + +- (void)testExceptionSuppressionInMethodBlock { + __block BOOL isCalled = NO; + + BlockBasedSonarPlugin *cat = [[BlockBasedSonarPlugin alloc] initIdentifier:@"PluginIdentifier" connect:^(id connection) { + + [connection receive:@"MethodName" withBlock:^(NSDictionary * dict, id responder) { + isCalled = YES; + NSArray *array = @[]; + [array objectAtIndex:10]; //This will throw an exception + }]; + } disconnect:nil runInBackground:YES]; + + [objcClient addPlugin:cat]; + [objcClient start]; + + folly::dynamic message = folly::dynamic::object("id", 1)("method", "execute")("params", folly::dynamic::object("api", "PluginIdentifier")("method", "MethodName")); + + XCTAssertNoThrow(socket->callbacks->onMessageReceived(message)); // This will call + XCTAssertTrue(isCalled); +} + +@end +#endif