| OLD | NEW |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "base/task.h" | 5 #include "base/task.h" |
| 6 #include "remoting/host/capturer_fake.h" | 6 #include "remoting/host/capturer_fake.h" |
| 7 #include "remoting/host/chromoting_host.h" | 7 #include "remoting/host/chromoting_host.h" |
| 8 #include "remoting/host/chromoting_host_context.h" | 8 #include "remoting/host/chromoting_host_context.h" |
| 9 #include "remoting/host/desktop_environment_fake.h" | |
| 10 #include "remoting/host/host_mock_objects.h" | 9 #include "remoting/host/host_mock_objects.h" |
| 11 #include "remoting/host/in_memory_host_config.h" | 10 #include "remoting/host/in_memory_host_config.h" |
| 12 #include "remoting/proto/video.pb.h" | 11 #include "remoting/proto/video.pb.h" |
| 13 #include "remoting/protocol/protocol_mock_objects.h" | 12 #include "remoting/protocol/protocol_mock_objects.h" |
| 14 #include "remoting/protocol/session_config.h" | 13 #include "remoting/protocol/session_config.h" |
| 14 #include "testing/gmock_mutant.h" |
| 15 #include "testing/gmock/include/gmock/gmock.h" | 15 #include "testing/gmock/include/gmock/gmock.h" |
| 16 #include "testing/gtest/include/gtest/gtest.h" | 16 #include "testing/gtest/include/gtest/gtest.h" |
| 17 | 17 |
| 18 using ::remoting::protocol::LocalLoginCredentials; | 18 using ::remoting::protocol::LocalLoginCredentials; |
| 19 using ::remoting::protocol::MockClientStub; | 19 using ::remoting::protocol::MockClientStub; |
| 20 using ::remoting::protocol::MockConnectionToClient; | 20 using ::remoting::protocol::MockConnectionToClient; |
| 21 using ::remoting::protocol::MockConnectionToClientEventHandler; | 21 using ::remoting::protocol::MockConnectionToClientEventHandler; |
| 22 using ::remoting::protocol::MockHostStub; | 22 using ::remoting::protocol::MockHostStub; |
| 23 using ::remoting::protocol::MockInputStub; | 23 using ::remoting::protocol::MockInputStub; |
| 24 using ::remoting::protocol::MockSession; | 24 using ::remoting::protocol::MockSession; |
| 25 using ::remoting::protocol::MockVideoStub; | 25 using ::remoting::protocol::MockVideoStub; |
| 26 using ::remoting::protocol::SessionConfig; | 26 using ::remoting::protocol::SessionConfig; |
| 27 | 27 |
| 28 using testing::_; | 28 using testing::_; |
| 29 using testing::AnyNumber; | 29 using testing::AnyNumber; |
| 30 using testing::CreateFunctor; |
| 30 using testing::DeleteArg; | 31 using testing::DeleteArg; |
| 31 using testing::DoAll; | 32 using testing::DoAll; |
| 32 using testing::InSequence; | 33 using testing::InSequence; |
| 33 using testing::InvokeWithoutArgs; | 34 using testing::InvokeWithoutArgs; |
| 34 using testing::Return; | 35 using testing::Return; |
| 35 | 36 |
| 36 namespace remoting { | 37 namespace remoting { |
| 37 | 38 |
| 38 namespace { | 39 namespace { |
| 39 | 40 |
| 40 void PostQuitTask(MessageLoop* message_loop) { | 41 void PostQuitTask(MessageLoop* message_loop) { |
| 41 message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask()); | 42 message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask()); |
| 42 } | 43 } |
| 43 | 44 |
| 44 void BeginSessionRequest(protocol::HostStub* host_stub) { | |
| 45 LocalLoginCredentials* credentials = | |
| 46 new LocalLoginCredentials(); | |
| 47 credentials->set_type(protocol::PASSWORD); | |
| 48 credentials->set_username("hello"); | |
| 49 | |
| 50 const std::string password = "world!"; | |
| 51 credentials->set_credential(password.data(), password.length()); | |
| 52 | |
| 53 host_stub->BeginSessionRequest( | |
| 54 credentials, | |
| 55 new DeleteTask<LocalLoginCredentials>(credentials)); | |
| 56 } | |
| 57 | |
| 58 // Run the task and delete it afterwards. This action is used to deal with | 45 // Run the task and delete it afterwards. This action is used to deal with |
| 59 // done callbacks. | 46 // done callbacks. |
| 60 ACTION(RunDoneTask) { | 47 ACTION(RunDoneTask) { |
| 61 arg1->Run(); | 48 arg1->Run(); |
| 62 delete arg1; | 49 delete arg1; |
| 63 } | 50 } |
| 64 | 51 |
| 65 ACTION_P(QuitMainMessageLoop, message_loop) { | 52 ACTION_P(QuitMainMessageLoop, message_loop) { |
| 66 PostQuitTask(message_loop); | 53 PostQuitTask(message_loop); |
| 67 } | 54 } |
| 68 | 55 |
| 69 } // namepace | 56 } // namespace |
| 70 | 57 |
| 71 class ChromotingHostTest : public testing::Test { | 58 class ChromotingHostTest : public testing::Test { |
| 72 public: | 59 public: |
| 73 ChromotingHostTest() { | 60 ChromotingHostTest() { |
| 74 } | 61 } |
| 75 | 62 |
| 76 virtual void SetUp() { | 63 virtual void SetUp() { |
| 77 config_ = new InMemoryHostConfig(); | 64 config_ = new InMemoryHostConfig(); |
| 78 ON_CALL(context_, main_message_loop()) | 65 ON_CALL(context_, main_message_loop()) |
| 79 .WillByDefault(Return(&message_loop_)); | 66 .WillByDefault(Return(&message_loop_)); |
| 80 ON_CALL(context_, encode_message_loop()) | 67 ON_CALL(context_, encode_message_loop()) |
| 81 .WillByDefault(Return(&message_loop_)); | 68 .WillByDefault(Return(&message_loop_)); |
| 82 ON_CALL(context_, network_message_loop()) | 69 ON_CALL(context_, network_message_loop()) |
| 83 .WillByDefault(Return(&message_loop_)); | 70 .WillByDefault(Return(&message_loop_)); |
| 84 EXPECT_CALL(context_, main_message_loop()) | 71 EXPECT_CALL(context_, main_message_loop()) |
| 85 .Times(AnyNumber()); | 72 .Times(AnyNumber()); |
| 86 EXPECT_CALL(context_, encode_message_loop()) | 73 EXPECT_CALL(context_, encode_message_loop()) |
| 87 .Times(AnyNumber()); | 74 .Times(AnyNumber()); |
| 88 EXPECT_CALL(context_, network_message_loop()) | 75 EXPECT_CALL(context_, network_message_loop()) |
| 89 .Times(AnyNumber()); | 76 .Times(AnyNumber()); |
| 90 | 77 |
| 91 Capturer* capturer = new CapturerFake(context_.main_message_loop()); | 78 Capturer* capturer = new CapturerFake(context_.main_message_loop()); |
| 92 host_stub_ = new MockHostStub(); | 79 host_stub_ = new MockHostStub(); |
| 80 host_stub2_ = new MockHostStub(); |
| 93 input_stub_ = new MockInputStub(); | 81 input_stub_ = new MockInputStub(); |
| 82 input_stub2_ = new MockInputStub(); |
| 94 DesktopEnvironment* desktop = | 83 DesktopEnvironment* desktop = |
| 95 new DesktopEnvironmentFake(capturer, input_stub_); | 84 new DesktopEnvironment(capturer, input_stub_); |
| 96 host_ = ChromotingHost::Create(&context_, config_, desktop); | 85 host_ = ChromotingHost::Create(&context_, config_, desktop); |
| 97 connection_ = new MockConnectionToClient( | 86 connection_ = new MockConnectionToClient( |
| 98 &message_loop_, &handler_, host_stub_, input_stub_); | 87 &message_loop_, &handler_, host_stub_, input_stub_); |
| 88 connection2_ = new MockConnectionToClient( |
| 89 &message_loop_, &handler_, host_stub2_, input_stub2_); |
| 99 session_ = new MockSession(); | 90 session_ = new MockSession(); |
| 91 session2_ = new MockSession(); |
| 100 session_config_.reset(SessionConfig::CreateDefault()); | 92 session_config_.reset(SessionConfig::CreateDefault()); |
| 93 session_config2_.reset(SessionConfig::CreateDefault()); |
| 101 | 94 |
| 102 ON_CALL(video_stub_, ProcessVideoPacket(_, _)) | 95 ON_CALL(video_stub_, ProcessVideoPacket(_, _)) |
| 103 .WillByDefault( | 96 .WillByDefault( |
| 104 DoAll(DeleteArg<0>(), DeleteArg<1>())); | 97 DoAll(DeleteArg<0>(), DeleteArg<1>())); |
| 98 ON_CALL(video_stub2_, ProcessVideoPacket(_, _)) |
| 99 .WillByDefault( |
| 100 DoAll(DeleteArg<0>(), DeleteArg<1>())); |
| 105 ON_CALL(*connection_.get(), video_stub()) | 101 ON_CALL(*connection_.get(), video_stub()) |
| 106 .WillByDefault(Return(&video_stub_)); | 102 .WillByDefault(Return(&video_stub_)); |
| 107 ON_CALL(*connection_.get(), client_stub()) | 103 ON_CALL(*connection_.get(), client_stub()) |
| 108 .WillByDefault(Return(&client_stub_)); | 104 .WillByDefault(Return(&client_stub_)); |
| 109 ON_CALL(*connection_.get(), session()) | 105 ON_CALL(*connection_.get(), session()) |
| 110 .WillByDefault(Return(session_)); | 106 .WillByDefault(Return(session_)); |
| 107 ON_CALL(*connection2_.get(), video_stub()) |
| 108 .WillByDefault(Return(&video_stub2_)); |
| 109 ON_CALL(*connection2_.get(), client_stub()) |
| 110 .WillByDefault(Return(&client_stub2_)); |
| 111 ON_CALL(*connection2_.get(), session()) |
| 112 .WillByDefault(Return(session2_)); |
| 111 ON_CALL(*session_.get(), config()) | 113 ON_CALL(*session_.get(), config()) |
| 112 .WillByDefault(Return(session_config_.get())); | 114 .WillByDefault(Return(session_config_.get())); |
| 115 ON_CALL(*session2_.get(), config()) |
| 116 .WillByDefault(Return(session_config2_.get())); |
| 113 EXPECT_CALL(*connection_.get(), video_stub()) | 117 EXPECT_CALL(*connection_.get(), video_stub()) |
| 114 .Times(AnyNumber()); | 118 .Times(AnyNumber()); |
| 115 EXPECT_CALL(*connection_.get(), client_stub()) | 119 EXPECT_CALL(*connection_.get(), client_stub()) |
| 116 .Times(AnyNumber()); | 120 .Times(AnyNumber()); |
| 117 EXPECT_CALL(*connection_.get(), session()) | 121 EXPECT_CALL(*connection_.get(), session()) |
| 118 .Times(AnyNumber()); | 122 .Times(AnyNumber()); |
| 123 EXPECT_CALL(*connection2_.get(), video_stub()) |
| 124 .Times(AnyNumber()); |
| 125 EXPECT_CALL(*connection2_.get(), client_stub()) |
| 126 .Times(AnyNumber()); |
| 127 EXPECT_CALL(*connection2_.get(), session()) |
| 128 .Times(AnyNumber()); |
| 119 EXPECT_CALL(*session_.get(), config()) | 129 EXPECT_CALL(*session_.get(), config()) |
| 120 .Times(AnyNumber()); | 130 .Times(AnyNumber()); |
| 131 EXPECT_CALL(*session2_.get(), config()) |
| 132 .Times(AnyNumber()); |
| 133 |
| 121 } | 134 } |
| 122 | 135 |
| 123 virtual void TearDown() { | 136 virtual void TearDown() { |
| 124 } | 137 } |
| 125 | 138 |
| 126 // Helper metjod to pretend a client is connected to ChromotingHost. | 139 // Helper method to pretend a client is connected to ChromotingHost. |
| 127 void SimulateClientConnection() { | 140 void SimulateClientConnection(int connection_index) { |
| 141 scoped_refptr<MockConnectionToClient> connection = |
| 142 (connection_index == 0) ? connection_ : connection2_; |
| 143 scoped_refptr<ClientSession> client = new ClientSession(host_.get(), |
| 144 connection); |
| 145 connection->set_host_stub(client.get()); |
| 146 |
| 128 context_.network_message_loop()->PostTask( | 147 context_.network_message_loop()->PostTask( |
| 129 FROM_HERE, | 148 FROM_HERE, |
| 130 NewRunnableMethod(host_.get(), | 149 NewRunnableMethod(host_.get(), |
| 131 &ChromotingHost::set_connection, | 150 &ChromotingHost::AddClient, |
| 132 connection_)); | 151 client)); |
| 133 context_.network_message_loop()->PostTask( | 152 context_.network_message_loop()->PostTask( |
| 134 FROM_HERE, | 153 FROM_HERE, |
| 135 NewRunnableMethod(host_.get(), | 154 NewRunnableMethod(host_.get(), |
| 136 &ChromotingHost::OnClientConnected, | 155 &ChromotingHost::OnClientConnected, |
| 137 connection_)); | 156 connection)); |
| 138 context_.network_message_loop()->PostTask( | 157 context_.network_message_loop()->PostTask( |
| 139 FROM_HERE, | 158 FROM_HERE, |
| 140 NewRunnableFunction(&BeginSessionRequest, host_->host_stub())); | 159 NewRunnableMethod(host_.get(), |
| 160 &ChromotingHost::LocalLoginSucceeded, |
| 161 connection)); |
| 141 } | 162 } |
| 142 | 163 |
| 143 // Helper method to remove a client connection from ChromotongHost. | 164 // Helper method to remove a client connection from ChromotingHost. |
| 144 void RemoveClientConnection() { | 165 void RemoveClientConnection() { |
| 145 context_.network_message_loop()->PostTask( | 166 context_.network_message_loop()->PostTask( |
| 146 FROM_HERE, | 167 FROM_HERE, |
| 147 NewRunnableMethod(host_.get(), | 168 NewRunnableMethod(host_.get(), |
| 148 &ChromotingHost::OnClientDisconnected, | 169 &ChromotingHost::OnClientDisconnected, |
| 149 connection_)); | 170 connection_)); |
| 150 } | 171 } |
| 151 | 172 |
| 152 protected: | 173 protected: |
| 153 MessageLoop message_loop_; | 174 MessageLoop message_loop_; |
| 154 MockConnectionToClientEventHandler handler_; | 175 MockConnectionToClientEventHandler handler_; |
| 155 scoped_refptr<ChromotingHost> host_; | 176 scoped_refptr<ChromotingHost> host_; |
| 156 scoped_refptr<InMemoryHostConfig> config_; | 177 scoped_refptr<InMemoryHostConfig> config_; |
| 157 MockChromotingHostContext context_; | 178 MockChromotingHostContext context_; |
| 158 scoped_refptr<MockConnectionToClient> connection_; | 179 scoped_refptr<MockConnectionToClient> connection_; |
| 159 scoped_refptr<MockSession> session_; | 180 scoped_refptr<MockSession> session_; |
| 160 scoped_ptr<SessionConfig> session_config_; | 181 scoped_ptr<SessionConfig> session_config_; |
| 161 MockVideoStub video_stub_; | 182 MockVideoStub video_stub_; |
| 162 MockClientStub client_stub_; | 183 MockClientStub client_stub_; |
| 163 MockHostStub* host_stub_; | 184 MockHostStub* host_stub_; |
| 164 MockInputStub* input_stub_; | 185 MockInputStub* input_stub_; |
| 186 scoped_refptr<MockConnectionToClient> connection2_; |
| 187 scoped_refptr<MockSession> session2_; |
| 188 scoped_ptr<SessionConfig> session_config2_; |
| 189 MockVideoStub video_stub2_; |
| 190 MockClientStub client_stub2_; |
| 191 MockHostStub* host_stub2_; |
| 192 MockInputStub* input_stub2_; |
| 165 }; | 193 }; |
| 166 | 194 |
| 167 TEST_F(ChromotingHostTest, StartAndShutdown) { | 195 TEST_F(ChromotingHostTest, StartAndShutdown) { |
| 168 host_->Start(NewRunnableFunction(&PostQuitTask, &message_loop_)); | 196 host_->Start(NewRunnableFunction(&PostQuitTask, &message_loop_)); |
| 169 | 197 |
| 170 message_loop_.PostTask(FROM_HERE, | 198 message_loop_.PostTask(FROM_HERE, |
| 171 NewRunnableMethod(host_.get(), | 199 NewRunnableMethod(host_.get(), |
| 172 &ChromotingHost::Shutdown)); | 200 &ChromotingHost::Shutdown)); |
| 173 message_loop_.Run(); | 201 message_loop_.Run(); |
| 174 } | 202 } |
| (...skipping 10 matching lines...) Expand all Loading... |
| 185 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) | 213 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) |
| 186 .WillOnce(DoAll( | 214 .WillOnce(DoAll( |
| 187 InvokeWithoutArgs(host_.get(), &ChromotingHost::Shutdown), | 215 InvokeWithoutArgs(host_.get(), &ChromotingHost::Shutdown), |
| 188 RunDoneTask())) | 216 RunDoneTask())) |
| 189 .RetiresOnSaturation(); | 217 .RetiresOnSaturation(); |
| 190 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) | 218 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) |
| 191 .Times(AnyNumber()); | 219 .Times(AnyNumber()); |
| 192 EXPECT_CALL(*connection_.get(), Disconnect()) | 220 EXPECT_CALL(*connection_.get(), Disconnect()) |
| 193 .RetiresOnSaturation(); | 221 .RetiresOnSaturation(); |
| 194 | 222 |
| 195 SimulateClientConnection(); | 223 SimulateClientConnection(0); |
| 196 message_loop_.Run(); | 224 message_loop_.Run(); |
| 197 } | 225 } |
| 198 | 226 |
| 199 TEST_F(ChromotingHostTest, Reconnect) { | 227 TEST_F(ChromotingHostTest, Reconnect) { |
| 200 host_->Start(NewRunnableFunction(&PostQuitTask, &message_loop_)); | 228 host_->Start(NewRunnableFunction(&PostQuitTask, &message_loop_)); |
| 201 | 229 |
| 202 EXPECT_CALL(client_stub_, BeginSessionResponse(_, _)) | 230 EXPECT_CALL(client_stub_, BeginSessionResponse(_, _)) |
| 203 .Times(2) | 231 .Times(2) |
| 204 .WillRepeatedly(RunDoneTask()); | 232 .WillRepeatedly(RunDoneTask()); |
| 205 | 233 |
| 206 // When the video packet is received we first disconnect the mock | 234 // When the video packet is received we first disconnect the mock |
| 207 // connection. | 235 // connection. |
| 208 { | 236 { |
| 209 InSequence s; | 237 InSequence s; |
| 210 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) | 238 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) |
| 211 .WillOnce(DoAll( | 239 .WillOnce(DoAll( |
| 212 InvokeWithoutArgs(this, | 240 InvokeWithoutArgs(this, |
| 213 &ChromotingHostTest::RemoveClientConnection), | 241 &ChromotingHostTest::RemoveClientConnection), |
| 214 RunDoneTask())) | 242 RunDoneTask())) |
| 215 .RetiresOnSaturation(); | 243 .RetiresOnSaturation(); |
| 216 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) | 244 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) |
| 217 .Times(AnyNumber()); | 245 .Times(AnyNumber()); |
| 218 } | 246 } |
| 219 | 247 |
| 220 // If Disconnect() is called we can break the main message loop. | 248 // If Disconnect() is called we can break the main message loop. |
| 221 EXPECT_CALL(*connection_.get(), Disconnect()) | 249 EXPECT_CALL(*connection_.get(), Disconnect()) |
| 222 .WillOnce(QuitMainMessageLoop(&message_loop_)) | 250 .WillOnce(QuitMainMessageLoop(&message_loop_)) |
| 223 .RetiresOnSaturation(); | 251 .RetiresOnSaturation(); |
| 224 | 252 |
| 225 SimulateClientConnection(); | 253 SimulateClientConnection(0); |
| 226 message_loop_.Run(); | 254 message_loop_.Run(); |
| 227 | 255 |
| 228 // Connect the client again. | 256 // Connect the client again. |
| 229 { | 257 { |
| 230 InSequence s; | 258 InSequence s; |
| 231 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) | 259 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) |
| 232 .WillOnce(DoAll( | 260 .WillOnce(DoAll( |
| 233 InvokeWithoutArgs(host_.get(), &ChromotingHost::Shutdown), | 261 InvokeWithoutArgs(host_.get(), &ChromotingHost::Shutdown), |
| 234 RunDoneTask())) | 262 RunDoneTask())) |
| 235 .RetiresOnSaturation(); | 263 .RetiresOnSaturation(); |
| 236 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) | 264 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) |
| 237 .Times(AnyNumber()); | 265 .Times(AnyNumber()); |
| 238 } | 266 } |
| 239 EXPECT_CALL(*connection_.get(), Disconnect()) | 267 EXPECT_CALL(*connection_.get(), Disconnect()) |
| 240 .RetiresOnSaturation(); | 268 .RetiresOnSaturation(); |
| 241 | 269 |
| 242 SimulateClientConnection(); | 270 SimulateClientConnection(0); |
| 243 message_loop_.Run(); | 271 message_loop_.Run(); |
| 244 } | 272 } |
| 245 | 273 |
| 274 TEST_F(ChromotingHostTest, ConnectTwice) { |
| 275 host_->Start(NewRunnableFunction(&PostQuitTask, &message_loop_)); |
| 276 |
| 277 EXPECT_CALL(client_stub_, BeginSessionResponse(_, _)) |
| 278 .Times(1) |
| 279 .WillRepeatedly(RunDoneTask()); |
| 280 |
| 281 EXPECT_CALL(client_stub2_, BeginSessionResponse(_, _)) |
| 282 .Times(1) |
| 283 .WillRepeatedly(RunDoneTask()); |
| 284 |
| 285 // When a video packet is received we connect the second mock |
| 286 // connection. |
| 287 { |
| 288 InSequence s; |
| 289 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) |
| 290 .WillOnce(DoAll( |
| 291 InvokeWithoutArgs( |
| 292 CreateFunctor( |
| 293 this, &ChromotingHostTest::SimulateClientConnection, 1)), |
| 294 RunDoneTask())) |
| 295 .RetiresOnSaturation(); |
| 296 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) |
| 297 .Times(AnyNumber()); |
| 298 EXPECT_CALL(video_stub2_, ProcessVideoPacket(_, _)) |
| 299 .WillOnce(DoAll( |
| 300 InvokeWithoutArgs(host_.get(), &ChromotingHost::Shutdown), |
| 301 RunDoneTask())) |
| 302 .RetiresOnSaturation(); |
| 303 EXPECT_CALL(video_stub2_, ProcessVideoPacket(_, _)) |
| 304 .Times(AnyNumber()); |
| 305 } |
| 306 |
| 307 EXPECT_CALL(*connection_.get(), Disconnect()) |
| 308 .RetiresOnSaturation(); |
| 309 EXPECT_CALL(*connection2_.get(), Disconnect()) |
| 310 .RetiresOnSaturation(); |
| 311 |
| 312 SimulateClientConnection(0); |
| 313 message_loop_.Run(); |
| 314 } |
| 315 |
| 246 } // namespace remoting | 316 } // namespace remoting |
| OLD | NEW |