// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "remoting/ios/bridge/client_instance.h" #include "base/compiler_specific.h" #include "base/run_loop.h" #include "base/mac/scoped_nsobject.h" #include "base/synchronization/waitable_event.h" #include "remoting/protocol/libjingle_transport_factory.h" #import "testing/gtest_mac.h" #include "remoting/ios/data_store.h" #include "remoting/ios/bridge/client_proxy.h" #include "remoting/ios/bridge/client_proxy_delegate_wrapper.h" @interface ClientProxyDelegateForClientInstanceTester : NSObject<ClientProxyDelegate> - (void)resetDidReceiveSomething; // Validating what was received is outside of the scope for this test unit. See // ClientProxyUnittest for those tests. @property(nonatomic, assign) BOOL didReceiveSomething; @end @implementation ClientProxyDelegateForClientInstanceTester @synthesize didReceiveSomething = _didReceiveSomething; - (void)resetDidReceiveSomething { _didReceiveSomething = false; } - (void)requestHostPin:(BOOL)pairingSupported { _didReceiveSomething = true; } - (void)connected { _didReceiveSomething = true; } - (void)connectionStatus:(NSString*)statusMessage { _didReceiveSomething = true; } - (void)connectionFailed:(NSString*)errorMessage { _didReceiveSomething = true; } - (void)applyFrame:(const webrtc::DesktopSize&)size stride:(NSInteger)stride data:(uint8_t*)data regions:(const std::vector<webrtc::DesktopRect>&)regions { _didReceiveSomething = true; } - (void)applyCursor:(const webrtc::DesktopSize&)size hotspot:(const webrtc::DesktopVector&)hotspot cursorData:(uint8_t*)data { _didReceiveSomething = true; } @end namespace remoting { namespace { const std::string kHostId = "HostIdTest"; const std::string kPairingId = "PairingIdTest"; const std::string kPairingSecret = "PairingSecretTest"; const std::string kSecretPin = "SecretPinTest"; // TODO(aboone) should be able to call RunLoop().RunUntilIdle() instead but // MessagePumpUIApplication::DoRun is marked NOTREACHED() void RunCFMessageLoop() { int result; do { // Repeat until no messages remain result = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, // Execute queued messages, do not wait for additional messages YES); // Do only one message at a time } while (result != kCFRunLoopRunStopped && result != kCFRunLoopRunFinished && result != kCFRunLoopRunTimedOut); } void SecretPinCallBack(const std::string& secret) { ASSERT_STREQ(kSecretPin.c_str(), secret.c_str()); } } // namespace class ClientInstanceTest : public ::testing::Test { protected: virtual void SetUp() OVERRIDE { testDelegate_.reset( [[ClientProxyDelegateForClientInstanceTester alloc] init]); proxy_.reset(new ClientProxy( [ClientProxyDelegateWrapper wrapDelegate:testDelegate_])); instance_ = new ClientInstance(proxy_->AsWeakPtr(), "", "", "", "", "", "", ""); } virtual void TearDown() OVERRIDE { // Ensure memory is not leaking // Notice Cleanup is safe to call, regardless of if Start() was ever called. instance_->Cleanup(); RunCFMessageLoop(); // An object on the network thread which owns a reference to |instance_| may // be cleaned up 'soon', but not immediately. Lets wait it out, up to 1 // second. for (int i = 0; i < 100; i++) { if (!instance_->HasOneRef()) { [NSThread sleepForTimeInterval:.01]; } else { break; } } // Remove the last reference from |instance_|, and destructor is called. ASSERT_TRUE(instance_->HasOneRef()); instance_ = NULL; } void AssertAcknowledged(BOOL wasAcknowledged) { ASSERT_EQ(wasAcknowledged, [testDelegate_ didReceiveSomething]); // Reset for the next test [testDelegate_ resetDidReceiveSomething]; } void TestStatusAndError(protocol::ConnectionToHost::State state, protocol::ErrorCode error) { instance_->OnConnectionState(state, error); AssertAcknowledged(true); } void TestConnectionStatus(protocol::ConnectionToHost::State state) { TestStatusAndError(state, protocol::ErrorCode::OK); TestStatusAndError(state, protocol::ErrorCode::PEER_IS_OFFLINE); TestStatusAndError(state, protocol::ErrorCode::SESSION_REJECTED); TestStatusAndError(state, protocol::ErrorCode::INCOMPATIBLE_PROTOCOL); TestStatusAndError(state, protocol::ErrorCode::AUTHENTICATION_FAILED); TestStatusAndError(state, protocol::ErrorCode::CHANNEL_CONNECTION_ERROR); TestStatusAndError(state, protocol::ErrorCode::SIGNALING_ERROR); TestStatusAndError(state, protocol::ErrorCode::SIGNALING_TIMEOUT); TestStatusAndError(state, protocol::ErrorCode::HOST_OVERLOAD); TestStatusAndError(state, protocol::ErrorCode::UNKNOWN_ERROR); } base::scoped_nsobject<ClientProxyDelegateForClientInstanceTester> testDelegate_; scoped_ptr<ClientProxy> proxy_; scoped_refptr<ClientInstance> instance_; }; TEST_F(ClientInstanceTest, Create) { // This is a test for memory leaking. Ensure a completely unused instance of // ClientInstance is destructed. ASSERT_TRUE(instance_ != NULL); ASSERT_TRUE(instance_->HasOneRef()); } TEST_F(ClientInstanceTest, CreateAndStart) { // This is a test for memory leaking. Ensure a properly used instance of // ClientInstance is destructed. ASSERT_TRUE(instance_ != NULL); ASSERT_TRUE(instance_->HasOneRef()); instance_->Start(); RunCFMessageLoop(); ASSERT_TRUE(!instance_->HasOneRef()); // more than one } TEST_F(ClientInstanceTest, SecretPin) { NSString* hostId = [NSString stringWithUTF8String:kHostId.c_str()]; NSString* pairingId = [NSString stringWithUTF8String:kPairingId.c_str()]; NSString* pairingSecret = [NSString stringWithUTF8String:kPairingSecret.c_str()]; DataStore* store = [DataStore sharedStore]; const HostPreferences* host = [store createHost:hostId]; host.pairId = pairingId; host.pairSecret = pairingSecret; [store saveChanges]; // Suggesting that our pairing Id is known, but since its obviously not we // expect it to be discarded when requesting the PIN. instance_ = new ClientInstance( proxy_->AsWeakPtr(), "", "", "", kHostId, "", kPairingId, kPairingSecret); instance_->Start(); RunCFMessageLoop(); instance_->FetchSecret(false, base::Bind(&SecretPinCallBack)); RunCFMessageLoop(); AssertAcknowledged(true); // The pairing information was discarded host = [store getHostForId:hostId]; ASSERT_NSEQ(@"", host.pairId); ASSERT_NSEQ(@"", host.pairSecret); instance_->ProvideSecret(kSecretPin, false); RunCFMessageLoop(); } TEST_F(ClientInstanceTest, NoProxy) { // After the proxy is released, we still expect quite a few functions to be // able to run, but not produce any output. Some of these are just being // executed for code coverage, the outputs are not pertinent to this test // unit. proxy_.reset(); instance_->Start(); RunCFMessageLoop(); instance_->FetchSecret(false, base::Bind(&SecretPinCallBack)); AssertAcknowledged(false); instance_->ProvideSecret(kSecretPin, false); AssertAcknowledged(false); instance_->PerformMouseAction( webrtc::DesktopVector(0, 0), webrtc::DesktopVector(0, 0), 0, false); AssertAcknowledged(false); instance_->PerformKeyboardAction(0, false); AssertAcknowledged(false); instance_->OnConnectionState(protocol::ConnectionToHost::State::CONNECTED, protocol::ErrorCode::OK); AssertAcknowledged(false); instance_->OnConnectionReady(false); AssertAcknowledged(false); instance_->OnRouteChanged("", protocol::TransportRoute()); AssertAcknowledged(false); // SetCapabilities requires a host connection to be established // instance_->SetCapabilities(""); // AssertAcknowledged(false); instance_->SetPairingResponse(protocol::PairingResponse()); AssertAcknowledged(false); instance_->DeliverHostMessage(protocol::ExtensionMessage()); AssertAcknowledged(false); ASSERT_TRUE(instance_->GetClipboardStub() != NULL); ASSERT_TRUE(instance_->GetCursorShapeStub() != NULL); ASSERT_TRUE(instance_->GetTokenFetcher("") == NULL); instance_->InjectClipboardEvent(protocol::ClipboardEvent()); AssertAcknowledged(false); instance_->SetCursorShape(protocol::CursorShapeInfo()); AssertAcknowledged(false); } TEST_F(ClientInstanceTest, OnConnectionStateINITIALIZING) { TestConnectionStatus(protocol::ConnectionToHost::State::INITIALIZING); } TEST_F(ClientInstanceTest, OnConnectionStateCONNECTING) { TestConnectionStatus(protocol::ConnectionToHost::State::CONNECTING); } TEST_F(ClientInstanceTest, OnConnectionStateAUTHENTICATED) { TestConnectionStatus(protocol::ConnectionToHost::State::AUTHENTICATED); } TEST_F(ClientInstanceTest, OnConnectionStateCONNECTED) { TestConnectionStatus(protocol::ConnectionToHost::State::CONNECTED); } TEST_F(ClientInstanceTest, OnConnectionStateFAILED) { TestConnectionStatus(protocol::ConnectionToHost::State::FAILED); } TEST_F(ClientInstanceTest, OnConnectionStateCLOSED) { TestConnectionStatus(protocol::ConnectionToHost::State::CLOSED); } TEST_F(ClientInstanceTest, OnConnectionReady) { instance_->OnConnectionReady(true); AssertAcknowledged(false); instance_->OnConnectionReady(false); AssertAcknowledged(false); } TEST_F(ClientInstanceTest, OnRouteChanged) { // Not expecting anything to happen protocol::TransportRoute route; route.type = protocol::TransportRoute::DIRECT; instance_->OnRouteChanged("", route); AssertAcknowledged(false); route.type = protocol::TransportRoute::STUN; instance_->OnRouteChanged("", route); AssertAcknowledged(false); route.type = protocol::TransportRoute::RELAY; instance_->OnRouteChanged("", route); AssertAcknowledged(false); } TEST_F(ClientInstanceTest, SetCursorShape) { instance_->SetCursorShape(protocol::CursorShapeInfo()); AssertAcknowledged(true); } } // namespace remoting