1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/bind.h" 6 #include "base/bind_helpers.h" 7 #include "base/location.h" 8 #include "base/memory/ref_counted.h" 9 #include "base/process/process.h" 10 #include "ipc/ipc_message.h" 11 #include "ipc/ipc_message_macros.h" 12 #include "ipc/ipc_platform_file.h" 13 #include "remoting/base/auto_thread_task_runner.h" 14 #include "remoting/host/chromoting_messages.h" 15 #include "remoting/host/daemon_process.h" 16 #include "remoting/host/desktop_session.h" 17 #include "testing/gmock_mutant.h" 18 #include "testing/gmock/include/gmock/gmock.h" 19 #include "testing/gtest/include/gtest/gtest.h" 20 21 using testing::_; 22 using testing::AnyNumber; 23 using testing::InSequence; 24 25 namespace remoting { 26 27 namespace { 28 29 enum Messages { 30 kMessageCrash = ChromotingDaemonMsg_Crash::ID, 31 kMessageConfiguration = ChromotingDaemonNetworkMsg_Configuration::ID, 32 kMessageConnectTerminal = ChromotingNetworkHostMsg_ConnectTerminal::ID, 33 kMessageDisconnectTerminal = ChromotingNetworkHostMsg_DisconnectTerminal::ID, 34 kMessageTerminalDisconnected = 35 ChromotingDaemonNetworkMsg_TerminalDisconnected::ID 36 }; 37 38 // Provides a public constructor allowing the test to create instances of 39 // DesktopSession directly. 40 class FakeDesktopSession : public DesktopSession { 41 public: 42 FakeDesktopSession(DaemonProcess* daemon_process, int id); 43 virtual ~FakeDesktopSession(); 44 45 virtual void SetScreenResolution( 46 const ScreenResolution& resolution) OVERRIDE {} 47 48 private: 49 DISALLOW_COPY_AND_ASSIGN(FakeDesktopSession); 50 }; 51 52 class MockDaemonProcess : public DaemonProcess { 53 public: 54 MockDaemonProcess( 55 scoped_refptr<AutoThreadTaskRunner> caller_task_runner, 56 scoped_refptr<AutoThreadTaskRunner> io_task_runner, 57 const base::Closure& stopped_callback); 58 virtual ~MockDaemonProcess(); 59 60 virtual scoped_ptr<DesktopSession> DoCreateDesktopSession( 61 int terminal_id, 62 const ScreenResolution& resolution, 63 bool virtual_terminal) OVERRIDE; 64 65 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; 66 virtual void SendToNetwork(IPC::Message* message) OVERRIDE; 67 68 MOCK_METHOD1(Received, void(const IPC::Message&)); 69 MOCK_METHOD1(Sent, void(const IPC::Message&)); 70 71 MOCK_METHOD3(OnDesktopSessionAgentAttached, 72 bool(int, base::ProcessHandle, IPC::PlatformFileForTransit)); 73 74 MOCK_METHOD1(DoCreateDesktopSessionPtr, DesktopSession*(int)); 75 MOCK_METHOD1(DoCrashNetworkProcess, void(const tracked_objects::Location&)); 76 MOCK_METHOD0(LaunchNetworkProcess, void()); 77 78 private: 79 DISALLOW_COPY_AND_ASSIGN(MockDaemonProcess); 80 }; 81 82 FakeDesktopSession::FakeDesktopSession(DaemonProcess* daemon_process, int id) 83 : DesktopSession(daemon_process, id) { 84 } 85 86 FakeDesktopSession::~FakeDesktopSession() { 87 } 88 89 MockDaemonProcess::MockDaemonProcess( 90 scoped_refptr<AutoThreadTaskRunner> caller_task_runner, 91 scoped_refptr<AutoThreadTaskRunner> io_task_runner, 92 const base::Closure& stopped_callback) 93 : DaemonProcess(caller_task_runner, io_task_runner, stopped_callback) { 94 } 95 96 MockDaemonProcess::~MockDaemonProcess() { 97 } 98 99 scoped_ptr<DesktopSession> MockDaemonProcess::DoCreateDesktopSession( 100 int terminal_id, 101 const ScreenResolution& resolution, 102 bool virtual_terminal) { 103 return scoped_ptr<DesktopSession>(DoCreateDesktopSessionPtr(terminal_id)); 104 } 105 106 bool MockDaemonProcess::OnMessageReceived(const IPC::Message& message) { 107 // Notify the mock method. 108 Received(message); 109 110 // Call the actual handler. 111 return DaemonProcess::OnMessageReceived(message); 112 } 113 114 void MockDaemonProcess::SendToNetwork(IPC::Message* message) { 115 // Notify the mock method. 116 Sent(*message); 117 delete message; 118 } 119 120 } // namespace 121 122 class DaemonProcessTest : public testing::Test { 123 public: 124 DaemonProcessTest(); 125 virtual ~DaemonProcessTest(); 126 127 virtual void SetUp() OVERRIDE; 128 virtual void TearDown() OVERRIDE; 129 130 // DaemonProcess mocks 131 DesktopSession* DoCreateDesktopSession(int terminal_id); 132 void DoCrashNetworkProcess(const tracked_objects::Location& location); 133 void LaunchNetworkProcess(); 134 135 // Deletes |daemon_process_|. 136 void DeleteDaemonProcess(); 137 138 // Quits |message_loop_|. 139 void QuitMessageLoop(); 140 141 void StartDaemonProcess(); 142 143 const DaemonProcess::DesktopSessionList& desktop_sessions() const { 144 return daemon_process_->desktop_sessions(); 145 } 146 147 protected: 148 base::MessageLoop message_loop_; 149 150 scoped_ptr<MockDaemonProcess> daemon_process_; 151 int terminal_id_; 152 }; 153 154 DaemonProcessTest::DaemonProcessTest() 155 : message_loop_(base::MessageLoop::TYPE_IO), terminal_id_(0) { 156 } 157 158 DaemonProcessTest::~DaemonProcessTest() { 159 } 160 161 void DaemonProcessTest::SetUp() { 162 scoped_refptr<AutoThreadTaskRunner> task_runner = new AutoThreadTaskRunner( 163 message_loop_.message_loop_proxy(), 164 base::Bind(&DaemonProcessTest::QuitMessageLoop, 165 base::Unretained(this))); 166 daemon_process_.reset( 167 new MockDaemonProcess(task_runner, task_runner, 168 base::Bind(&DaemonProcessTest::DeleteDaemonProcess, 169 base::Unretained(this)))); 170 171 // Set up daemon process mocks. 172 EXPECT_CALL(*daemon_process_, DoCreateDesktopSessionPtr(_)) 173 .Times(AnyNumber()) 174 .WillRepeatedly(Invoke(this, &DaemonProcessTest::DoCreateDesktopSession)); 175 EXPECT_CALL(*daemon_process_, DoCrashNetworkProcess(_)) 176 .Times(AnyNumber()) 177 .WillRepeatedly(Invoke(this, &DaemonProcessTest::DoCrashNetworkProcess)); 178 EXPECT_CALL(*daemon_process_, LaunchNetworkProcess()) 179 .Times(AnyNumber()) 180 .WillRepeatedly(Invoke(this, &DaemonProcessTest::LaunchNetworkProcess)); 181 } 182 183 void DaemonProcessTest::TearDown() { 184 daemon_process_->Stop(); 185 message_loop_.Run(); 186 } 187 188 DesktopSession* DaemonProcessTest::DoCreateDesktopSession(int terminal_id) { 189 return new FakeDesktopSession(daemon_process_.get(), terminal_id); 190 } 191 192 void DaemonProcessTest::DoCrashNetworkProcess( 193 const tracked_objects::Location& location) { 194 daemon_process_->SendToNetwork( 195 new ChromotingDaemonMsg_Crash(location.function_name(), 196 location.file_name(), 197 location.line_number())); 198 } 199 200 void DaemonProcessTest::LaunchNetworkProcess() { 201 terminal_id_ = 0; 202 daemon_process_->OnChannelConnected(0); 203 } 204 205 void DaemonProcessTest::DeleteDaemonProcess() { 206 daemon_process_.reset(); 207 } 208 209 void DaemonProcessTest::QuitMessageLoop() { 210 message_loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); 211 } 212 213 void DaemonProcessTest::StartDaemonProcess() { 214 // DaemonProcess::Initialize() sets up the config watcher that this test does 215 // not support. Launch the process directly. 216 daemon_process_->LaunchNetworkProcess(); 217 } 218 219 MATCHER_P(Message, type, "") { 220 return arg.type() == static_cast<uint32>(type); 221 } 222 223 TEST_F(DaemonProcessTest, OpenClose) { 224 InSequence s; 225 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 226 EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); 227 EXPECT_CALL(*daemon_process_, Received(Message(kMessageDisconnectTerminal))); 228 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageTerminalDisconnected))); 229 230 StartDaemonProcess(); 231 232 int id = terminal_id_++; 233 ScreenResolution resolution; 234 235 EXPECT_TRUE(daemon_process_->OnMessageReceived( 236 ChromotingNetworkHostMsg_ConnectTerminal(id, resolution, false))); 237 EXPECT_EQ(1u, desktop_sessions().size()); 238 EXPECT_EQ(id, desktop_sessions().front()->id()); 239 240 EXPECT_TRUE(daemon_process_->OnMessageReceived( 241 ChromotingNetworkHostMsg_DisconnectTerminal(id))); 242 EXPECT_TRUE(desktop_sessions().empty()); 243 } 244 245 TEST_F(DaemonProcessTest, CallCloseDesktopSession) { 246 InSequence s; 247 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 248 EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); 249 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageTerminalDisconnected))); 250 251 StartDaemonProcess(); 252 253 int id = terminal_id_++; 254 ScreenResolution resolution; 255 256 EXPECT_TRUE(daemon_process_->OnMessageReceived( 257 ChromotingNetworkHostMsg_ConnectTerminal(id, resolution, false))); 258 EXPECT_EQ(1u, desktop_sessions().size()); 259 EXPECT_EQ(id, desktop_sessions().front()->id()); 260 261 daemon_process_->CloseDesktopSession(id); 262 EXPECT_TRUE(desktop_sessions().empty()); 263 } 264 265 // Sends two CloseDesktopSession messages and expects the second one to be 266 // ignored. 267 TEST_F(DaemonProcessTest, DoubleDisconnectTerminal) { 268 InSequence s; 269 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 270 EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); 271 EXPECT_CALL(*daemon_process_, Received(Message(kMessageDisconnectTerminal))); 272 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageTerminalDisconnected))); 273 EXPECT_CALL(*daemon_process_, Received(Message(kMessageDisconnectTerminal))); 274 275 StartDaemonProcess(); 276 277 int id = terminal_id_++; 278 ScreenResolution resolution; 279 280 EXPECT_TRUE(daemon_process_->OnMessageReceived( 281 ChromotingNetworkHostMsg_ConnectTerminal(id, resolution, false))); 282 EXPECT_EQ(1u, desktop_sessions().size()); 283 EXPECT_EQ(id, desktop_sessions().front()->id()); 284 285 EXPECT_TRUE(daemon_process_->OnMessageReceived( 286 ChromotingNetworkHostMsg_DisconnectTerminal(id))); 287 EXPECT_TRUE(desktop_sessions().empty()); 288 289 EXPECT_TRUE(daemon_process_->OnMessageReceived( 290 ChromotingNetworkHostMsg_DisconnectTerminal(id))); 291 EXPECT_TRUE(desktop_sessions().empty()); 292 } 293 294 // Tries to close an invalid terminal ID and expects the network process to be 295 // restarted. 296 TEST_F(DaemonProcessTest, InvalidDisconnectTerminal) { 297 InSequence s; 298 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 299 EXPECT_CALL(*daemon_process_, Received(Message(kMessageDisconnectTerminal))); 300 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageCrash))) 301 .WillOnce(InvokeWithoutArgs(this, 302 &DaemonProcessTest::LaunchNetworkProcess)); 303 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 304 305 StartDaemonProcess(); 306 307 int id = terminal_id_++; 308 309 EXPECT_TRUE(daemon_process_->OnMessageReceived( 310 ChromotingNetworkHostMsg_DisconnectTerminal(id))); 311 EXPECT_TRUE(desktop_sessions().empty()); 312 EXPECT_EQ(0, terminal_id_); 313 } 314 315 // Tries to open an invalid terminal ID and expects the network process to be 316 // restarted. 317 TEST_F(DaemonProcessTest, InvalidConnectTerminal) { 318 InSequence s; 319 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 320 EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); 321 EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); 322 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageCrash))) 323 .WillOnce(InvokeWithoutArgs(this, 324 &DaemonProcessTest::LaunchNetworkProcess)); 325 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 326 327 StartDaemonProcess(); 328 329 int id = terminal_id_++; 330 ScreenResolution resolution; 331 332 EXPECT_TRUE(daemon_process_->OnMessageReceived( 333 ChromotingNetworkHostMsg_ConnectTerminal(id, resolution, false))); 334 EXPECT_EQ(1u, desktop_sessions().size()); 335 EXPECT_EQ(id, desktop_sessions().front()->id()); 336 337 EXPECT_TRUE(daemon_process_->OnMessageReceived( 338 ChromotingNetworkHostMsg_ConnectTerminal(id, resolution, false))); 339 EXPECT_TRUE(desktop_sessions().empty()); 340 EXPECT_EQ(0, terminal_id_); 341 } 342 343 } // namespace remoting 344