1 // Copyright 2013 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 "apps/app_shim/app_shim_host_manager_mac.h" 6 7 #include <unistd.h> 8 9 #include "apps/app_shim/app_shim_messages.h" 10 #include "apps/app_shim/test/app_shim_host_manager_test_api_mac.h" 11 #include "base/files/file_path.h" 12 #include "base/logging.h" 13 #include "base/path_service.h" 14 #include "chrome/browser/browser_process.h" 15 #include "chrome/browser/profiles/profile.h" 16 #include "chrome/browser/ui/browser.h" 17 #include "chrome/common/chrome_paths.h" 18 #include "chrome/common/mac/app_mode_common.h" 19 #include "chrome/test/base/in_process_browser_test.h" 20 #include "content/public/test/test_utils.h" 21 #include "ipc/ipc_channel_proxy.h" 22 #include "ipc/ipc_listener.h" 23 #include "ipc/ipc_message.h" 24 25 namespace { 26 27 const char kTestAppMode[] = "test_app"; 28 29 // A test version of the AppShimController IPC client in chrome_main_app_mode. 30 class TestShimClient : public IPC::Listener { 31 public: 32 TestShimClient(); 33 virtual ~TestShimClient(); 34 35 template <class T> 36 void Send(const T& message) { 37 channel_->Send(message); 38 } 39 40 private: 41 // IPC::Listener overrides: 42 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; 43 virtual void OnChannelError() OVERRIDE; 44 45 base::Thread io_thread_; 46 scoped_ptr<IPC::ChannelProxy> channel_; 47 48 DISALLOW_COPY_AND_ASSIGN(TestShimClient); 49 }; 50 51 TestShimClient::TestShimClient() : io_thread_("TestShimClientIO") { 52 base::Thread::Options io_thread_options; 53 io_thread_options.message_loop_type = base::MessageLoop::TYPE_IO; 54 io_thread_.StartWithOptions(io_thread_options); 55 56 base::FilePath user_data_dir; 57 CHECK(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)); 58 base::FilePath symlink_path = 59 user_data_dir.Append(app_mode::kAppShimSocketSymlinkName); 60 61 base::FilePath socket_path; 62 CHECK(base::ReadSymbolicLink(symlink_path, &socket_path)); 63 app_mode::VerifySocketPermissions(socket_path); 64 65 IPC::ChannelHandle handle(socket_path.value()); 66 channel_ = IPC::ChannelProxy::Create(handle, 67 IPC::Channel::MODE_NAMED_CLIENT, 68 this, 69 io_thread_.message_loop_proxy().get()); 70 } 71 72 TestShimClient::~TestShimClient() {} 73 74 bool TestShimClient::OnMessageReceived(const IPC::Message& message) { 75 return true; 76 } 77 78 void TestShimClient::OnChannelError() { 79 // Client should not get any channel errors for the current set of tests. 80 PLOG(FATAL) << "ChannelError"; 81 } 82 83 // Browser Test for AppShimHostManager to test IPC interactions across the 84 // UNIX domain socket. 85 class AppShimHostManagerBrowserTest : public InProcessBrowserTest, 86 public apps::AppShimHandler { 87 public: 88 AppShimHostManagerBrowserTest(); 89 virtual ~AppShimHostManagerBrowserTest(); 90 91 protected: 92 // Wait for OnShimLaunch, then send a quit, and wait for the response. Used to 93 // test launch behavior. 94 void RunAndExitGracefully(); 95 96 // InProcessBrowserTest overrides: 97 virtual void SetUpOnMainThread() OVERRIDE; 98 virtual void TearDownOnMainThread() OVERRIDE; 99 100 // AppShimHandler overrides: 101 virtual void OnShimLaunch(apps::AppShimHandler::Host* host, 102 apps::AppShimLaunchType launch_type, 103 const std::vector<base::FilePath>& files) OVERRIDE; 104 virtual void OnShimClose(apps::AppShimHandler::Host* host) OVERRIDE {} 105 virtual void OnShimFocus(apps::AppShimHandler::Host* host, 106 apps::AppShimFocusType focus_type, 107 const std::vector<base::FilePath>& files) OVERRIDE {} 108 virtual void OnShimSetHidden(apps::AppShimHandler::Host* host, 109 bool hidden) OVERRIDE {} 110 virtual void OnShimQuit(apps::AppShimHandler::Host* host) OVERRIDE; 111 112 scoped_ptr<TestShimClient> test_client_; 113 std::vector<base::FilePath> last_launch_files_; 114 apps::AppShimLaunchType last_launch_type_; 115 116 private: 117 scoped_refptr<content::MessageLoopRunner> runner_; 118 119 int launch_count_; 120 int quit_count_; 121 122 DISALLOW_COPY_AND_ASSIGN(AppShimHostManagerBrowserTest); 123 }; 124 125 AppShimHostManagerBrowserTest::AppShimHostManagerBrowserTest() 126 : last_launch_type_(apps::APP_SHIM_LAUNCH_NUM_TYPES), 127 launch_count_(0), 128 quit_count_(0) { 129 } 130 131 AppShimHostManagerBrowserTest::~AppShimHostManagerBrowserTest() { 132 } 133 134 void AppShimHostManagerBrowserTest::RunAndExitGracefully() { 135 runner_ = new content::MessageLoopRunner(); 136 EXPECT_EQ(0, launch_count_); 137 runner_->Run(); // Will stop in OnShimLaunch(). 138 EXPECT_EQ(1, launch_count_); 139 140 runner_ = new content::MessageLoopRunner(); 141 test_client_->Send(new AppShimHostMsg_QuitApp); 142 EXPECT_EQ(0, quit_count_); 143 runner_->Run(); // Will stop in OnShimQuit(). 144 EXPECT_EQ(1, quit_count_); 145 146 test_client_.reset(); 147 } 148 149 void AppShimHostManagerBrowserTest::SetUpOnMainThread() { 150 // Can't do this in the constructor, it needs a BrowserProcess. 151 apps::AppShimHandler::RegisterHandler(kTestAppMode, this); 152 } 153 154 void AppShimHostManagerBrowserTest::TearDownOnMainThread() { 155 apps::AppShimHandler::RemoveHandler(kTestAppMode); 156 } 157 158 void AppShimHostManagerBrowserTest::OnShimLaunch( 159 apps::AppShimHandler::Host* host, 160 apps::AppShimLaunchType launch_type, 161 const std::vector<base::FilePath>& files) { 162 host->OnAppLaunchComplete(apps::APP_SHIM_LAUNCH_SUCCESS); 163 ++launch_count_; 164 last_launch_type_ = launch_type; 165 last_launch_files_ = files; 166 runner_->Quit(); 167 } 168 169 void AppShimHostManagerBrowserTest::OnShimQuit( 170 apps::AppShimHandler::Host* host) { 171 ++quit_count_; 172 runner_->Quit(); 173 } 174 175 // Test regular launch, which would ask Chrome to launch the app. 176 IN_PROC_BROWSER_TEST_F(AppShimHostManagerBrowserTest, LaunchNormal) { 177 test_client_.reset(new TestShimClient()); 178 test_client_->Send(new AppShimHostMsg_LaunchApp( 179 browser()->profile()->GetPath(), 180 kTestAppMode, 181 apps::APP_SHIM_LAUNCH_NORMAL, 182 std::vector<base::FilePath>())); 183 184 RunAndExitGracefully(); 185 EXPECT_EQ(apps::APP_SHIM_LAUNCH_NORMAL, last_launch_type_); 186 EXPECT_TRUE(last_launch_files_.empty()); 187 } 188 189 // Test register-only launch, used when Chrome has already launched the app. 190 IN_PROC_BROWSER_TEST_F(AppShimHostManagerBrowserTest, LaunchRegisterOnly) { 191 test_client_.reset(new TestShimClient()); 192 test_client_->Send(new AppShimHostMsg_LaunchApp( 193 browser()->profile()->GetPath(), 194 kTestAppMode, 195 apps::APP_SHIM_LAUNCH_REGISTER_ONLY, 196 std::vector<base::FilePath>())); 197 198 RunAndExitGracefully(); 199 EXPECT_EQ(apps::APP_SHIM_LAUNCH_REGISTER_ONLY, last_launch_type_); 200 EXPECT_TRUE(last_launch_files_.empty()); 201 } 202 203 // Ensure the domain socket can be created in a fresh user data dir. 204 IN_PROC_BROWSER_TEST_F(AppShimHostManagerBrowserTest, 205 PRE_ReCreate) { 206 test::AppShimHostManagerTestApi test_api( 207 g_browser_process->platform_part()->app_shim_host_manager()); 208 EXPECT_TRUE(test_api.factory()); 209 } 210 211 // Ensure the domain socket can be re-created after a prior browser process has 212 // quit. 213 IN_PROC_BROWSER_TEST_F(AppShimHostManagerBrowserTest, 214 ReCreate) { 215 test::AppShimHostManagerTestApi test_api( 216 g_browser_process->platform_part()->app_shim_host_manager()); 217 EXPECT_TRUE(test_api.factory()); 218 } 219 220 // Tests for the files created by AppShimHostManager. 221 class AppShimHostManagerBrowserTestSocketFiles 222 : public AppShimHostManagerBrowserTest { 223 public: 224 AppShimHostManagerBrowserTestSocketFiles() {} 225 226 protected: 227 base::FilePath directory_in_tmp_; 228 base::FilePath symlink_path_; 229 230 private: 231 virtual bool SetUpUserDataDirectory() OVERRIDE; 232 virtual void TearDownInProcessBrowserTestFixture() OVERRIDE; 233 234 DISALLOW_COPY_AND_ASSIGN(AppShimHostManagerBrowserTestSocketFiles); 235 }; 236 237 bool AppShimHostManagerBrowserTestSocketFiles::SetUpUserDataDirectory() { 238 // Create an existing symlink. It should be replaced by AppShimHostManager. 239 base::FilePath user_data_dir; 240 EXPECT_TRUE(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)); 241 symlink_path_ = user_data_dir.Append(app_mode::kAppShimSocketSymlinkName); 242 base::FilePath temp_dir; 243 PathService::Get(base::DIR_TEMP, &temp_dir); 244 EXPECT_TRUE(base::CreateSymbolicLink(temp_dir.Append("chrome-XXXXXX"), 245 symlink_path_)); 246 return AppShimHostManagerBrowserTest::SetUpUserDataDirectory(); 247 } 248 249 void AppShimHostManagerBrowserTestSocketFiles:: 250 TearDownInProcessBrowserTestFixture() { 251 // Check that created files have been deleted. 252 EXPECT_FALSE(base::PathExists(directory_in_tmp_)); 253 EXPECT_FALSE(base::PathExists(symlink_path_)); 254 } 255 256 IN_PROC_BROWSER_TEST_F(AppShimHostManagerBrowserTestSocketFiles, 257 ReplacesSymlinkAndCleansUpFiles) { 258 // Get the directory created by AppShimHostManager. 259 test::AppShimHostManagerTestApi test_api( 260 g_browser_process->platform_part()->app_shim_host_manager()); 261 directory_in_tmp_ = test_api.directory_in_tmp(); 262 263 // Check that socket files have been created. 264 EXPECT_TRUE(base::PathExists(directory_in_tmp_)); 265 EXPECT_TRUE(base::PathExists(symlink_path_)); 266 267 // Check that the symlink has been replaced. 268 base::FilePath socket_path; 269 ASSERT_TRUE(base::ReadSymbolicLink(symlink_path_, &socket_path)); 270 EXPECT_EQ(app_mode::kAppShimSocketShortName, socket_path.BaseName().value()); 271 } 272 273 } // namespace 274