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::MessageLoopForIO message_loop_; 149 150 scoped_ptr<MockDaemonProcess> daemon_process_; 151 int terminal_id_; 152 }; 153 154 DaemonProcessTest::DaemonProcessTest() : terminal_id_(0) { 155 } 156 157 DaemonProcessTest::~DaemonProcessTest() { 158 } 159 160 void DaemonProcessTest::SetUp() { 161 scoped_refptr<AutoThreadTaskRunner> task_runner = new AutoThreadTaskRunner( 162 message_loop_.message_loop_proxy(), 163 base::Bind(&DaemonProcessTest::QuitMessageLoop, 164 base::Unretained(this))); 165 daemon_process_.reset( 166 new MockDaemonProcess(task_runner, task_runner, 167 base::Bind(&DaemonProcessTest::DeleteDaemonProcess, 168 base::Unretained(this)))); 169 170 // Set up daemon process mocks. 171 EXPECT_CALL(*daemon_process_, DoCreateDesktopSessionPtr(_)) 172 .Times(AnyNumber()) 173 .WillRepeatedly(Invoke(this, &DaemonProcessTest::DoCreateDesktopSession)); 174 EXPECT_CALL(*daemon_process_, DoCrashNetworkProcess(_)) 175 .Times(AnyNumber()) 176 .WillRepeatedly(Invoke(this, &DaemonProcessTest::DoCrashNetworkProcess)); 177 EXPECT_CALL(*daemon_process_, LaunchNetworkProcess()) 178 .Times(AnyNumber()) 179 .WillRepeatedly(Invoke(this, &DaemonProcessTest::LaunchNetworkProcess)); 180 } 181 182 void DaemonProcessTest::TearDown() { 183 daemon_process_->Stop(); 184 message_loop_.Run(); 185 } 186 187 DesktopSession* DaemonProcessTest::DoCreateDesktopSession(int terminal_id) { 188 return new FakeDesktopSession(daemon_process_.get(), terminal_id); 189 } 190 191 void DaemonProcessTest::DoCrashNetworkProcess( 192 const tracked_objects::Location& location) { 193 daemon_process_->SendToNetwork( 194 new ChromotingDaemonMsg_Crash(location.function_name(), 195 location.file_name(), 196 location.line_number())); 197 } 198 199 void DaemonProcessTest::LaunchNetworkProcess() { 200 terminal_id_ = 0; 201 daemon_process_->OnChannelConnected(0); 202 } 203 204 void DaemonProcessTest::DeleteDaemonProcess() { 205 daemon_process_.reset(); 206 } 207 208 void DaemonProcessTest::QuitMessageLoop() { 209 message_loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); 210 } 211 212 void DaemonProcessTest::StartDaemonProcess() { 213 // DaemonProcess::Initialize() sets up the config watcher that this test does 214 // not support. Launch the process directly. 215 daemon_process_->LaunchNetworkProcess(); 216 } 217 218 MATCHER_P(Message, type, "") { 219 return arg.type() == static_cast<uint32>(type); 220 } 221 222 TEST_F(DaemonProcessTest, OpenClose) { 223 InSequence s; 224 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 225 EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); 226 EXPECT_CALL(*daemon_process_, Received(Message(kMessageDisconnectTerminal))); 227 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageTerminalDisconnected))); 228 229 StartDaemonProcess(); 230 231 int id = terminal_id_++; 232 ScreenResolution resolution; 233 234 EXPECT_TRUE(daemon_process_->OnMessageReceived( 235 ChromotingNetworkHostMsg_ConnectTerminal(id, resolution, false))); 236 EXPECT_EQ(1u, desktop_sessions().size()); 237 EXPECT_EQ(id, desktop_sessions().front()->id()); 238 239 EXPECT_TRUE(daemon_process_->OnMessageReceived( 240 ChromotingNetworkHostMsg_DisconnectTerminal(id))); 241 EXPECT_TRUE(desktop_sessions().empty()); 242 } 243 244 TEST_F(DaemonProcessTest, CallCloseDesktopSession) { 245 InSequence s; 246 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 247 EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); 248 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageTerminalDisconnected))); 249 250 StartDaemonProcess(); 251 252 int id = terminal_id_++; 253 ScreenResolution resolution; 254 255 EXPECT_TRUE(daemon_process_->OnMessageReceived( 256 ChromotingNetworkHostMsg_ConnectTerminal(id, resolution, false))); 257 EXPECT_EQ(1u, desktop_sessions().size()); 258 EXPECT_EQ(id, desktop_sessions().front()->id()); 259 260 daemon_process_->CloseDesktopSession(id); 261 EXPECT_TRUE(desktop_sessions().empty()); 262 } 263 264 // Sends two CloseDesktopSession messages and expects the second one to be 265 // ignored. 266 TEST_F(DaemonProcessTest, DoubleDisconnectTerminal) { 267 InSequence s; 268 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 269 EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); 270 EXPECT_CALL(*daemon_process_, Received(Message(kMessageDisconnectTerminal))); 271 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageTerminalDisconnected))); 272 EXPECT_CALL(*daemon_process_, Received(Message(kMessageDisconnectTerminal))); 273 274 StartDaemonProcess(); 275 276 int id = terminal_id_++; 277 ScreenResolution resolution; 278 279 EXPECT_TRUE(daemon_process_->OnMessageReceived( 280 ChromotingNetworkHostMsg_ConnectTerminal(id, resolution, false))); 281 EXPECT_EQ(1u, desktop_sessions().size()); 282 EXPECT_EQ(id, desktop_sessions().front()->id()); 283 284 EXPECT_TRUE(daemon_process_->OnMessageReceived( 285 ChromotingNetworkHostMsg_DisconnectTerminal(id))); 286 EXPECT_TRUE(desktop_sessions().empty()); 287 288 EXPECT_TRUE(daemon_process_->OnMessageReceived( 289 ChromotingNetworkHostMsg_DisconnectTerminal(id))); 290 EXPECT_TRUE(desktop_sessions().empty()); 291 } 292 293 // Tries to close an invalid terminal ID and expects the network process to be 294 // restarted. 295 TEST_F(DaemonProcessTest, InvalidDisconnectTerminal) { 296 InSequence s; 297 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 298 EXPECT_CALL(*daemon_process_, Received(Message(kMessageDisconnectTerminal))); 299 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageCrash))) 300 .WillOnce(InvokeWithoutArgs(this, 301 &DaemonProcessTest::LaunchNetworkProcess)); 302 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 303 304 StartDaemonProcess(); 305 306 int id = terminal_id_++; 307 308 EXPECT_TRUE(daemon_process_->OnMessageReceived( 309 ChromotingNetworkHostMsg_DisconnectTerminal(id))); 310 EXPECT_TRUE(desktop_sessions().empty()); 311 EXPECT_EQ(0, terminal_id_); 312 } 313 314 // Tries to open an invalid terminal ID and expects the network process to be 315 // restarted. 316 TEST_F(DaemonProcessTest, InvalidConnectTerminal) { 317 InSequence s; 318 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 319 EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); 320 EXPECT_CALL(*daemon_process_, Received(Message(kMessageConnectTerminal))); 321 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageCrash))) 322 .WillOnce(InvokeWithoutArgs(this, 323 &DaemonProcessTest::LaunchNetworkProcess)); 324 EXPECT_CALL(*daemon_process_, Sent(Message(kMessageConfiguration))); 325 326 StartDaemonProcess(); 327 328 int id = terminal_id_++; 329 ScreenResolution resolution; 330 331 EXPECT_TRUE(daemon_process_->OnMessageReceived( 332 ChromotingNetworkHostMsg_ConnectTerminal(id, resolution, false))); 333 EXPECT_EQ(1u, desktop_sessions().size()); 334 EXPECT_EQ(id, desktop_sessions().front()->id()); 335 336 EXPECT_TRUE(daemon_process_->OnMessageReceived( 337 ChromotingNetworkHostMsg_ConnectTerminal(id, resolution, false))); 338 EXPECT_TRUE(desktop_sessions().empty()); 339 EXPECT_EQ(0, terminal_id_); 340 } 341 342 } // namespace remoting 343