// Copyright (c) 2012 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 "base/message_loop.h" #include "remoting/base/auto_thread_task_runner.h" #include "remoting/base/constants.h" #include "remoting/host/audio_capturer.h" #include "remoting/host/client_session.h" #include "remoting/host/desktop_environment.h" #include "remoting/host/host_mock_objects.h" #include "remoting/protocol/protocol_mock_objects.h" #include "testing/gtest/include/gtest/gtest.h" namespace remoting { using protocol::MockConnectionToClient; using protocol::MockClientStub; using protocol::MockHostStub; using protocol::MockInputStub; using protocol::MockSession; using protocol::MockVideoStub; using protocol::SessionConfig; using testing::_; using testing::AnyNumber; using testing::DeleteArg; using testing::Expectation; using testing::InSequence; using testing::Return; using testing::ReturnRef; class ClientSessionTest : public testing::Test { public: ClientSessionTest() : event_executor_(NULL) {} virtual void SetUp() OVERRIDE { ui_task_runner_ = new AutoThreadTaskRunner( message_loop_.message_loop_proxy(), base::Bind(&ClientSessionTest::QuitMainMessageLoop, base::Unretained(this))); client_jid_ = "user@domain/rest-of-jid"; desktop_environment_factory_.reset(new MockDesktopEnvironmentFactory()); EXPECT_CALL(*desktop_environment_factory_, CreatePtr(_)) .Times(AnyNumber()) .WillRepeatedly(Invoke(this, &ClientSessionTest::CreateDesktopEnvironment)); // Set up a large default screen size that won't affect most tests. screen_size_.set(1000, 1000); session_config_ = SessionConfig::ForTest(); // Mock protocol::Session APIs called directly by ClientSession. protocol::MockSession* session = new MockSession(); EXPECT_CALL(*session, config()).WillRepeatedly(ReturnRef(session_config_)); EXPECT_CALL(*session, jid()).WillRepeatedly(ReturnRef(client_jid_)); EXPECT_CALL(*session, SetEventHandler(_)); // Mock protocol::ConnectionToClient APIs called directly by ClientSession. // HostStub is not touched by ClientSession, so we can safely pass NULL. scoped_ptr<MockConnectionToClient> connection( new MockConnectionToClient(session, NULL)); EXPECT_CALL(*connection, session()).WillRepeatedly(Return(session)); EXPECT_CALL(*connection, client_stub()) .WillRepeatedly(Return(&client_stub_)); EXPECT_CALL(*connection, video_stub()).WillRepeatedly(Return(&video_stub_)); EXPECT_CALL(*connection, Disconnect()); connection_ = connection.get(); client_session_ = new ClientSession( &session_event_handler_, ui_task_runner_, // Audio thread. ui_task_runner_, // Capture thread. ui_task_runner_, // Encode thread. ui_task_runner_, // Network thread. connection.PassAs<protocol::ConnectionToClient>(), desktop_environment_factory_.get(), base::TimeDelta()); } virtual void TearDown() OVERRIDE { // MockClientSessionEventHandler won't trigger Stop, so fake it. client_session_->Stop(base::Bind( &ClientSessionTest::OnClientStopped, base::Unretained(this))); // Run message loop before destroying because the session is destroyed // asynchronously. ui_task_runner_ = NULL; message_loop_.Run(); // Verify that the client session has been stopped. EXPECT_TRUE(client_session_.get() == NULL); } protected: DesktopEnvironment* CreateDesktopEnvironment(ClientSession* client) { MockVideoFrameCapturer* capturer = new MockVideoFrameCapturer(); EXPECT_CALL(*capturer, Start(_)); EXPECT_CALL(*capturer, Stop()); EXPECT_CALL(*capturer, InvalidateRegion(_)).Times(AnyNumber()); EXPECT_CALL(*capturer, CaptureFrame()).Times(AnyNumber()); EXPECT_CALL(*capturer, size_most_recent()) .WillRepeatedly(ReturnRef(screen_size_)); EXPECT_TRUE(!event_executor_); event_executor_ = new MockEventExecutor(); return new DesktopEnvironment(scoped_ptr<AudioCapturer>(NULL), scoped_ptr<EventExecutor>(event_executor_), scoped_ptr<VideoFrameCapturer>(capturer)); } void DisconnectClientSession() { client_session_->Disconnect(); // MockSession won't trigger OnConnectionClosed, so fake it. client_session_->OnConnectionClosed(client_session_->connection(), protocol::OK); } void QuitMainMessageLoop() { message_loop_.PostTask(FROM_HERE, MessageLoop::QuitClosure()); } void OnClientStopped() { client_session_ = NULL; } // Message loop passed to |client_session_| to perform all functions on. MessageLoop message_loop_; scoped_refptr<AutoThreadTaskRunner> ui_task_runner_; // ClientSession instance under test. scoped_refptr<ClientSession> client_session_; // ClientSession::EventHandler mock for use in tests. MockClientSessionEventHandler session_event_handler_; // Screen size that the fake VideoFrameCapturer should report. SkISize screen_size_; // Storage for values to be returned by the protocol::Session mock. SessionConfig session_config_; std::string client_jid_; // Stubs returned to |client_session_| components by |connection_|. MockClientStub client_stub_; MockVideoStub video_stub_; // DesktopEnvironment owns |event_executor_|, but input injection tests need // to express expectations on it. MockEventExecutor* event_executor_; // ClientSession owns |connection_| but tests need it to inject fake events. MockConnectionToClient* connection_; scoped_ptr<MockDesktopEnvironmentFactory> desktop_environment_factory_; }; MATCHER_P2(EqualsClipboardEvent, m, d, "") { return (strcmp(arg.mime_type().c_str(), m) == 0 && memcmp(arg.data().data(), d, arg.data().size()) == 0); } TEST_F(ClientSessionTest, ClipboardStubFilter) { protocol::ClipboardEvent clipboard_event1; clipboard_event1.set_mime_type(kMimeTypeTextUtf8); clipboard_event1.set_data("a"); protocol::ClipboardEvent clipboard_event2; clipboard_event2.set_mime_type(kMimeTypeTextUtf8); clipboard_event2.set_data("b"); protocol::ClipboardEvent clipboard_event3; clipboard_event3.set_mime_type(kMimeTypeTextUtf8); clipboard_event3.set_data("c"); InSequence s; EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_)); EXPECT_CALL(*event_executor_, StartPtr(_)); EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_)); EXPECT_CALL(*event_executor_, InjectClipboardEvent(EqualsClipboardEvent( kMimeTypeTextUtf8, "b"))); EXPECT_CALL(session_event_handler_, OnSessionClosed(_)); // This event should not get through to the clipboard stub, // because the client isn't authenticated yet. connection_->clipboard_stub()->InjectClipboardEvent(clipboard_event1); client_session_->OnConnectionAuthenticated(client_session_->connection()); client_session_->OnConnectionChannelsConnected(client_session_->connection()); // This event should get through to the clipboard stub. connection_->clipboard_stub()->InjectClipboardEvent(clipboard_event2); DisconnectClientSession(); // This event should not get through to the clipboard stub, // because the client has disconnected. connection_->clipboard_stub()->InjectClipboardEvent(clipboard_event3); } MATCHER_P2(EqualsUsbEvent, usb_keycode, pressed, "") { return arg.usb_keycode() == (unsigned int)usb_keycode && arg.pressed() == pressed; } MATCHER_P2(EqualsMouseEvent, x, y, "") { return arg.x() == x && arg.y() == y; } MATCHER_P2(EqualsMouseButtonEvent, button, down, "") { return arg.button() == button && arg.button_down() == down; } TEST_F(ClientSessionTest, InputStubFilter) { protocol::KeyEvent key_event1; key_event1.set_pressed(true); key_event1.set_usb_keycode(1); protocol::KeyEvent key_event2_down; key_event2_down.set_pressed(true); key_event2_down.set_usb_keycode(2); protocol::KeyEvent key_event2_up; key_event2_up.set_pressed(false); key_event2_up.set_usb_keycode(2); protocol::KeyEvent key_event3; key_event3.set_pressed(true); key_event3.set_usb_keycode(3); protocol::MouseEvent mouse_event1; mouse_event1.set_x(100); mouse_event1.set_y(101); protocol::MouseEvent mouse_event2; mouse_event2.set_x(200); mouse_event2.set_y(201); protocol::MouseEvent mouse_event3; mouse_event3.set_x(300); mouse_event3.set_y(301); InSequence s; EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_)); EXPECT_CALL(*event_executor_, StartPtr(_)); EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_)); EXPECT_CALL(*event_executor_, InjectKeyEvent(EqualsUsbEvent(2, true))); EXPECT_CALL(*event_executor_, InjectKeyEvent(EqualsUsbEvent(2, false))); EXPECT_CALL(*event_executor_, InjectMouseEvent(EqualsMouseEvent(200, 201))); EXPECT_CALL(session_event_handler_, OnSessionClosed(_)); // These events should not get through to the input stub, // because the client isn't authenticated yet. connection_->input_stub()->InjectKeyEvent(key_event1); connection_->input_stub()->InjectMouseEvent(mouse_event1); client_session_->OnConnectionAuthenticated(client_session_->connection()); client_session_->OnConnectionChannelsConnected(client_session_->connection()); // These events should get through to the input stub. connection_->input_stub()->InjectKeyEvent(key_event2_down); connection_->input_stub()->InjectKeyEvent(key_event2_up); connection_->input_stub()->InjectMouseEvent(mouse_event2); DisconnectClientSession(); // These events should not get through to the input stub, // because the client has disconnected. connection_->input_stub()->InjectKeyEvent(key_event3); connection_->input_stub()->InjectMouseEvent(mouse_event3); } TEST_F(ClientSessionTest, LocalInputTest) { protocol::MouseEvent mouse_event1; mouse_event1.set_x(100); mouse_event1.set_y(101); protocol::MouseEvent mouse_event2; mouse_event2.set_x(200); mouse_event2.set_y(201); protocol::MouseEvent mouse_event3; mouse_event3.set_x(300); mouse_event3.set_y(301); InSequence s; EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_)); EXPECT_CALL(*event_executor_, StartPtr(_)); EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_)); EXPECT_CALL(*event_executor_, InjectMouseEvent(EqualsMouseEvent(100, 101))); EXPECT_CALL(*event_executor_, InjectMouseEvent(EqualsMouseEvent(200, 201))); EXPECT_CALL(session_event_handler_, OnSessionClosed(_)); client_session_->OnConnectionAuthenticated(client_session_->connection()); client_session_->OnConnectionChannelsConnected(client_session_->connection()); // This event should get through to the input stub. connection_->input_stub()->InjectMouseEvent(mouse_event1); // This one should too because the local event echoes the remote one. client_session_->LocalMouseMoved(SkIPoint::Make(mouse_event1.x(), mouse_event1.y())); connection_->input_stub()->InjectMouseEvent(mouse_event2); // This one should not. client_session_->LocalMouseMoved(SkIPoint::Make(mouse_event1.x(), mouse_event1.y())); connection_->input_stub()->InjectMouseEvent(mouse_event3); // TODO(jamiewalch): Verify that remote inputs are re-enabled eventually // (via dependency injection, not sleep!) DisconnectClientSession(); } TEST_F(ClientSessionTest, RestoreEventState) { protocol::KeyEvent key1; key1.set_pressed(true); key1.set_usb_keycode(1); protocol::KeyEvent key2; key2.set_pressed(true); key2.set_usb_keycode(2); protocol::MouseEvent mousedown; mousedown.set_button(protocol::MouseEvent::BUTTON_LEFT); mousedown.set_button_down(true); InSequence s; EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_)); EXPECT_CALL(*event_executor_, StartPtr(_)); EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_)); EXPECT_CALL(*event_executor_, InjectKeyEvent(EqualsUsbEvent(1, true))); EXPECT_CALL(*event_executor_, InjectKeyEvent(EqualsUsbEvent(2, true))); EXPECT_CALL(*event_executor_, InjectMouseEvent(EqualsMouseButtonEvent( protocol::MouseEvent::BUTTON_LEFT, true))); EXPECT_CALL(*event_executor_, InjectKeyEvent(EqualsUsbEvent(1, false))); EXPECT_CALL(*event_executor_, InjectKeyEvent(EqualsUsbEvent(2, false))); EXPECT_CALL(*event_executor_, InjectMouseEvent(EqualsMouseButtonEvent( protocol::MouseEvent::BUTTON_LEFT, false))); EXPECT_CALL(session_event_handler_, OnSessionClosed(_)); client_session_->OnConnectionAuthenticated(client_session_->connection()); client_session_->OnConnectionChannelsConnected(client_session_->connection()); connection_->input_stub()->InjectKeyEvent(key1); connection_->input_stub()->InjectKeyEvent(key2); connection_->input_stub()->InjectMouseEvent(mousedown); DisconnectClientSession(); } TEST_F(ClientSessionTest, ClampMouseEvents) { screen_size_.set(200, 100); EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_)); EXPECT_CALL(*event_executor_, StartPtr(_)); Expectation connected = EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_)); EXPECT_CALL(session_event_handler_, OnSessionClosed(_)); client_session_->OnConnectionAuthenticated(client_session_->connection()); client_session_->OnConnectionChannelsConnected(client_session_->connection()); int input_x[3] = { -999, 100, 999 }; int expected_x[3] = { 0, 100, 199 }; int input_y[3] = { -999, 50, 999 }; int expected_y[3] = { 0, 50, 99 }; protocol::MouseEvent event; for (int j = 0; j < 3; j++) { for (int i = 0; i < 3; i++) { event.set_x(input_x[i]); event.set_y(input_y[j]); connected = EXPECT_CALL(*event_executor_, InjectMouseEvent(EqualsMouseEvent(expected_x[i], expected_y[j]))) .After(connected); connection_->input_stub()->InjectMouseEvent(event); } } DisconnectClientSession(); } } // namespace remoting